Files
flatpak/common/flatpak-transaction.c
Matthew Leeds 5cff4500a2 transaction: Fix error handling for related refs
This commit fixes the handling of errors from installing/updating
related refs during a transaction, so that they're treated as non-fatal,
and so that the operation is skipped if the primary operation fails. The
current behavior is that a failure to install/update a related ref
causes the whole transaction to fail, and even after a failure to
install/update the primary ref the related ref install/update is
attempted.

I hit this error when doing an offline USB app install, when the USB
repo has an older version of the runtime and the runtime's locale
extension than what's in the local repo. Without this commit, the
failure to update the runtime (due to it being a downgrade) is treated
as a warning, but the failure to update the runtime locale is treated as
an error. With this commit, the runtime update failure is still treated
as a warning, and the locale update is not attempted. This is better
behavior because the locale extension update (or even install) is not
critical to the app install.

Closes: #1979
Approved by: alexlarsson
2018-08-17 09:34:34 +00:00

2559 lines
84 KiB
C

/*
* Copyright © 2016 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <stdio.h>
#include <glib/gi18n.h>
#include <libsoup/soup.h>
#include "flatpak-transaction-private.h"
#include "flatpak-installation-private.h"
#include "flatpak-utils-private.h"
#include "flatpak-error.h"
/**
* SECTION:flatpak-transaction
* @Title: FlatpakTransaction
* @Short_description: Transaction information
*
* FlatpakTransaction is an object representing an install/update
* transaction. You create an object like this using flatpak_transaction_new_for_installation()
* and then you add all the operations (installs, updates, etc) you wish to do. Then
* you start the transaction with flatpak_transaction_run() which will resolve all kinds
* of dependencies and report progress and status while downloading and installing these.
*
* A transaction is a blocking operation, and all signals are emitted in the same thread.
* This means you should either handle the signals directly (say, by doing blocking console
* interaction, or by just returning without interaction), or run the operation in a separate
* thread and do your own forwarding to the GUI thread.
*/
/* This is an internal-only element of FlatpakTransactionOperationType */
#define FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE FLATPAK_TRANSACTION_OPERATION_LAST_TYPE + 1
struct _FlatpakTransactionOperation
{
GObject parent;
char *remote;
char *ref;
/* NULL means unspecified (normally keep whatever was there before), [] means force everything */
char **subpaths;
char *commit;
GFile *bundle;
GBytes *external_metadata;
FlatpakTransactionOperationType kind;
gboolean non_fatal;
gboolean failed;
gboolean skip;
gboolean resolved;
char *resolved_commit;
GBytes *resolved_metadata;
GKeyFile *resolved_metakey;
GBytes *resolved_old_metadata;
GKeyFile *resolved_old_metakey;
int run_after_count;
int run_after_prio; /* Higher => run later (when it becomes runnable). Used to run related ops (runtime extensions) before deps (apps using the runtime) */
GList *run_before_ops;
FlatpakTransactionOperation *fail_if_op_fails; /* main app/runtime for related extensions, runtime for apps */
};
typedef struct _FlatpakTransactionPrivate FlatpakTransactionPrivate;
typedef struct _BundleData BundleData;
struct _BundleData
{
GFile *file;
GBytes *gpg_data;
};
struct _FlatpakTransactionPrivate
{
GObject parent;
FlatpakInstallation *installation;
FlatpakDir *dir;
GHashTable *last_op_for_ref;
GHashTable *remote_states; /* (element-type utf8 FlatpakRemoteState) */
GPtrArray *extra_dependency_dirs;
GList *ops;
GPtrArray *added_origin_remotes;
GList *flatpakrefs; /* GKeyFiles */
GList *bundles; /* BundleData */
FlatpakTransactionOperation *current_op;
gboolean no_pull;
gboolean no_deploy;
gboolean disable_static_deltas;
gboolean disable_prune;
gboolean disable_deps;
gboolean disable_related;
gboolean reinstall;
gboolean force_uninstall;
char *default_arch;
};
enum {
NEW_OPERATION,
OPERATION_DONE,
OPERATION_ERROR,
CHOOSE_REMOTE_FOR_REF,
END_OF_LIFED,
READY,
ADD_NEW_REMOTE,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_INSTALLATION,
};
struct _FlatpakTransactionProgress
{
GObject parent;
OstreeAsyncProgress *ostree_progress;
char *status;
gboolean estimating;
int progress;
gboolean done;
};
enum {
CHANGED,
LAST_PROGRESS_SIGNAL
};
static BundleData *
bundle_data_new (GFile *file,
GBytes *gpg_data)
{
BundleData *data = g_new0 (BundleData, 1);
data->file = g_object_ref (file);
if (gpg_data)
data->gpg_data = g_object_ref (gpg_data);
return data;
}
static void
bundle_data_free (BundleData *data)
{
g_clear_object (&data->file);
g_clear_object (&data->gpg_data);
g_free (data);
}
static guint progress_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (FlatpakTransactionProgress, flatpak_transaction_progress, G_TYPE_OBJECT)
void
flatpak_transaction_progress_set_update_frequency (FlatpakTransactionProgress *self,
guint update_frequency)
{
g_object_set_data (G_OBJECT (self->ostree_progress), "update-frequency", GUINT_TO_POINTER (update_frequency));
}
char *
flatpak_transaction_progress_get_status (FlatpakTransactionProgress *self)
{
return g_strdup (self->status);
}
gboolean
flatpak_transaction_progress_get_is_estimating (FlatpakTransactionProgress *self)
{
return self->estimating;
}
int
flatpak_transaction_progress_get_progress (FlatpakTransactionProgress *self)
{
return self->progress;
}
static void
flatpak_transaction_progress_finalize (GObject *object)
{
FlatpakTransactionProgress *self = (FlatpakTransactionProgress *) object;
g_free (self->status);
g_object_unref (self->ostree_progress);
G_OBJECT_CLASS (flatpak_transaction_progress_parent_class)->finalize (object);
}
static void
flatpak_transaction_progress_class_init (FlatpakTransactionProgressClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = flatpak_transaction_progress_finalize;
/**
* FlatpakTransactionProgress::changed:
* @object: A #FlatpakTransactionProgress
*
* Emitted when some detail of the progress object changes, you can call the various methods to get the current status.
*/
progress_signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
static void
got_progress_cb (const char *status,
guint progress,
gboolean estimating,
gpointer user_data)
{
FlatpakTransactionProgress *p = user_data;
g_free (p->status);
p->status = g_strdup (status);
p->progress = progress;
p->estimating = estimating;
if (!p->done)
g_signal_emit (p, progress_signals[CHANGED], 0);
}
static void
flatpak_transaction_progress_init (FlatpakTransactionProgress *self)
{
self->status = g_strdup ("Initializing");
self->estimating = TRUE;
self->ostree_progress = flatpak_progress_new (got_progress_cb, self);
}
static void
flatpak_transaction_progress_done (FlatpakTransactionProgress *self)
{
ostree_async_progress_finish (self->ostree_progress);
self->done = TRUE;
}
static FlatpakTransactionProgress *
flatpak_transaction_progress_new (void)
{
return g_object_new (FLATPAK_TYPE_TRANSACTION_PROGRESS, NULL);
}
static guint signals[LAST_SIGNAL] = { 0 };
static void initable_iface_init (GInitableIface *initable_iface);
G_DEFINE_TYPE_WITH_CODE (FlatpakTransaction, flatpak_transaction, G_TYPE_OBJECT,
G_ADD_PRIVATE (FlatpakTransaction)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
static gboolean
transaction_is_local_only (FlatpakTransaction *self,
FlatpakTransactionOperationType kind)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
return priv->no_pull || kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL;
}
static gboolean
remote_name_is_file (const char *remote_name)
{
return remote_name != NULL &&
g_str_has_prefix (remote_name, "file://");
}
/**
* flatpak_transaction_add_dependency_source:
* @self: a #FlatpakTransaction
* @installation: a #FlatpakInstallation
*
* Adds an extra installation as a source for application dependencies.
* This means that applications can be installed in this transaction relying
* on runtimes from this additional installation (wheres it would normally
* install required runtimes that are not installed in the installation
* the transaction works on).
*/
void
flatpak_transaction_add_dependency_source (FlatpakTransaction *self,
FlatpakInstallation *installation)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_ptr_array_add (priv->extra_dependency_dirs,
flatpak_installation_clone_dir_noensure (installation));
}
/**
* flatpak_transaction_add_default_dependency_sources:
* @self: a #FlatpakTransaction
*
* Similar to flatpak_transaction_add_dependency_source(), but adds
* all the default installations, which means all the defined system-wide
* (but not per-user) installations.
*/
void
flatpak_transaction_add_default_dependency_sources (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GPtrArray) system_dirs = NULL;
GFile *path = flatpak_dir_get_path (priv->dir);
int i;
system_dirs = flatpak_dir_get_system_list (NULL, NULL);
if (system_dirs == NULL)
return;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
GFile *system_path = flatpak_dir_get_path (system_dir);
if (g_file_equal (path, system_path))
continue;
g_ptr_array_add (priv->extra_dependency_dirs, g_object_ref (system_dir));
}
}
/* Check if the ref is in the dir, or in the extra dependency source dir, in case its a
* user-dir or another system-wide installation. We want to avoid depending
* on user-installed things when installing to the system dir.
*/
static gboolean
ref_is_installed (FlatpakTransaction *self,
const char *ref,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GFile) deploy_dir = NULL;
FlatpakDir *dir = priv->dir;
int i;
deploy_dir = flatpak_dir_get_if_deployed (dir, ref, NULL, NULL);
if (deploy_dir != NULL)
return TRUE;
for (i = 0; i < priv->extra_dependency_dirs->len; i++)
{
FlatpakDir *dependency_dir = g_ptr_array_index (priv->extra_dependency_dirs, i);
deploy_dir = flatpak_dir_get_if_deployed (dependency_dir, ref, NULL, NULL);
if (deploy_dir != NULL)
return TRUE;
}
return FALSE;
}
static gboolean
dir_ref_is_installed (FlatpakDir *dir, const char *ref, char **remote_out, GVariant **deploy_data_out)
{
g_autoptr(GVariant) deploy_data = NULL;
deploy_data = flatpak_dir_get_deploy_data (dir, ref, NULL, NULL);
if (deploy_data == NULL)
return FALSE;
if (remote_out)
*remote_out = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
if (deploy_data_out)
*deploy_data_out = g_variant_ref (deploy_data);
return TRUE;
}
G_DEFINE_TYPE (FlatpakTransactionOperation, flatpak_transaction_operation, G_TYPE_OBJECT)
static void
flatpak_transaction_operation_finalize (GObject *object)
{
FlatpakTransactionOperation *self = (FlatpakTransactionOperation *) object;
g_free (self->remote);
g_free (self->ref);
g_free (self->commit);
g_strfreev (self->subpaths);
g_clear_object (&self->bundle);
if (self->external_metadata)
g_bytes_unref (self->external_metadata);
g_free (self->resolved_commit);
if (self->resolved_metadata)
g_bytes_unref (self->resolved_metadata);
if (self->resolved_metakey)
g_key_file_unref (self->resolved_metakey);
if (self->resolved_old_metadata)
g_bytes_unref (self->resolved_old_metadata);
if (self->resolved_old_metakey)
g_key_file_unref (self->resolved_old_metakey);
g_list_free (self->run_before_ops);
G_OBJECT_CLASS (flatpak_transaction_operation_parent_class)->finalize (object);
}
static void
flatpak_transaction_operation_class_init (FlatpakTransactionOperationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = flatpak_transaction_operation_finalize;
}
static void
flatpak_transaction_operation_init (FlatpakTransactionOperation *self)
{
}
static FlatpakTransactionOperation *
flatpak_transaction_operation_new (const char *remote,
const char *ref,
const char **subpaths,
const char *commit,
GFile *bundle,
FlatpakTransactionOperationType kind)
{
FlatpakTransactionOperation *self;
self = g_object_new (FLATPAK_TYPE_TRANSACTION_OPERATION, NULL);
self->remote = g_strdup (remote);
self->ref = g_strdup (ref);
self->subpaths = g_strdupv ((char **) subpaths);
self->commit = g_strdup (commit);
if (bundle)
self->bundle = g_object_ref (bundle);
self->kind = kind;
return self;
}
FlatpakTransactionOperationType
flatpak_transaction_operation_get_operation_type (FlatpakTransactionOperation *self)
{
return self->kind;
}
const char *
flatpak_transaction_operation_get_ref (FlatpakTransactionOperation *self)
{
return self->ref;
}
const char *
flatpak_transaction_operation_get_remote (FlatpakTransactionOperation *self)
{
return self->remote;
}
const char *
flatpak_transaction_operation_type_to_string (FlatpakTransactionOperationType kind)
{
if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
return "install";
if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
return "update";
if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
return "install-bundle";
if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
return "uninstall";
return NULL;
}
/**
* flatpak_transaction_operation_get_bundle_path:
* @self: a #FlatpakTransactionOperation
*
* Gets the path to the bundle.
*
* Returns: (transfer none): the bundle #GFile or %NULL
*/
GFile *
flatpak_transaction_operation_get_bundle_path (FlatpakTransactionOperation *self)
{
return self->bundle;
}
const char *
flatpak_transaction_operation_get_commit (FlatpakTransactionOperation *self)
{
return self->resolved_commit;
}
GKeyFile *
flatpak_transaction_operation_get_metadata (FlatpakTransactionOperation *self)
{
return self->resolved_metakey;
}
GKeyFile *
flatpak_transaction_operation_get_old_metadata (FlatpakTransactionOperation *self)
{
return self->resolved_old_metakey;
}
gboolean
flatpak_transaction_is_empty (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
return priv->ops == NULL;
}
static void
flatpak_transaction_finalize (GObject *object)
{
FlatpakTransaction *self = (FlatpakTransaction *) object;
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_clear_object (&priv->installation);
g_list_free_full (priv->flatpakrefs, (GDestroyNotify) g_key_file_unref);
g_list_free_full (priv->bundles, (GDestroyNotify) bundle_data_free);
g_free (priv->default_arch);
g_hash_table_unref (priv->last_op_for_ref);
g_hash_table_unref (priv->remote_states);
g_list_free_full (priv->ops, (GDestroyNotify) g_object_unref);
g_object_unref (priv->dir);
g_ptr_array_unref (priv->added_origin_remotes);
g_ptr_array_free (priv->extra_dependency_dirs, TRUE);
G_OBJECT_CLASS (flatpak_transaction_parent_class)->finalize (object);
}
static void
flatpak_transaction_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FlatpakTransaction *self = FLATPAK_TRANSACTION (object);
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
switch (prop_id)
{
case PROP_INSTALLATION:
g_clear_object (&priv->installation);
priv->installation = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
signal_accumulator_false_abort (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean continue_emission;
gboolean signal_continue;
signal_continue = g_value_get_boolean (handler_return);
g_value_set_boolean (return_accu, signal_continue);
continue_emission = signal_continue;
return continue_emission;
}
static void
flatpak_transaction_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
FlatpakTransaction *self = FLATPAK_TRANSACTION (object);
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
switch (prop_id)
{
case PROP_INSTALLATION:
g_value_set_object (value, priv->installation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
flatpak_transaction_ready (FlatpakTransaction *transaction)
{
return TRUE;
}
static gboolean
flatpak_transaction_add_new_remote (FlatpakTransaction *transaction,
FlatpakTransactionRemoteReason reason,
const char *from_id,
const char *suggested_remote_name,
const char *url)
{
return FALSE;
}
static void
flatpak_transaction_class_init (FlatpakTransactionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
klass->ready = flatpak_transaction_ready;
klass->add_new_remote = flatpak_transaction_add_new_remote;
object_class->finalize = flatpak_transaction_finalize;
object_class->get_property = flatpak_transaction_get_property;
object_class->set_property = flatpak_transaction_set_property;
g_object_class_install_property (object_class,
PROP_INSTALLATION,
g_param_spec_object ("installation",
"Installation",
"The installation instance",
FLATPAK_TYPE_INSTALLATION,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
/**
* FlatpakTransaction::new-operation:
* @object: A #FlatpakTransaction
* @ref: The ref the operation will be working on
* @remote: The ref the operation will be working on
* @bundle: The bundle path (or %NULL)
* @operation_type: A #FlatpakTransactionOperationType specifying operation type
* @progress: A #FlatpakTransactionProgress
*/
signals[NEW_OPERATION] =
g_signal_new ("new-operation",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, new_operation),
NULL, NULL,
NULL,
G_TYPE_NONE, 2, FLATPAK_TYPE_TRANSACTION_OPERATION, FLATPAK_TYPE_TRANSACTION_PROGRESS);
/**
* FlatpakTransaction::operation-error:
* @object: A #FlatpakTransaction
* @ref: The ref the operation was working on
* @remote: The remote
* @operation_type: A #FlatpakTransactionOperationType specifying operation type
* @error: A #GError
* @details: A #FlatpakTransactionErrorDetails with Details about the error
*
* Returns: the %TRUE to contine transaction, %FALSE to stop
*/
signals[OPERATION_ERROR] =
g_signal_new ("operation-error",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, operation_error),
NULL, NULL,
NULL,
G_TYPE_BOOLEAN, 3, FLATPAK_TYPE_TRANSACTION_OPERATION, G_TYPE_ERROR, G_TYPE_INT);
/**
* FlatpakTransaction::operation-done:
* @object: A #FlatpakTransaction
* @ref: The ref the operation was working on
* @remote: The remote
* @operation_type: A #FlatpakTransactionOperationType specifying operation type
* @commit: The new commit checksum
* @result: A #FlatpakTransactionResult giving details about the result
*
*/
signals[OPERATION_DONE] =
g_signal_new ("operation-done",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, operation_done),
NULL, NULL,
NULL,
G_TYPE_NONE, 2, FLATPAK_TYPE_TRANSACTION_OPERATION, G_TYPE_INT);
/**
* FlatpakTransaction::choose-remote-for-ref:
* @object: A #FlatpakTransaction
* @for_ref: The ref we are installing
* @runtime_ref: The ref we are looking for
* @remotes: the remotes that has the ref, sorted in prio order
*
* Returns: the index of the remote to use, or -1 to not pick one (and fail)
*/
signals[CHOOSE_REMOTE_FOR_REF] =
g_signal_new ("choose-remote-for-ref",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, choose_remote_for_ref),
NULL, NULL,
NULL,
G_TYPE_INT, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRV);
/**
* FlatpakTransaction::end-of-lifed:
* @object: A #FlatpakTransaction
* @ref: The ref we are installing
* @reason: The eol reason, or %NULL
* @rebase: The new name, if rebased, or %NULL
*/
signals[END_OF_LIFED] =
g_signal_new ("end-of-lifed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, end_of_lifed),
NULL, NULL,
NULL,
G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
/**
* FlatpakTransaction::ready:
* @object: A #FlatpakTransaction
*
* This is is emitted when all the refs involved in the operation have been
* resolved to commits. At this point flatpak_transaction_get_operations()
* will return all the operations that will be executed as part of the
* transaction. If this returns FALSE, the operation is aborted.
*/
signals[READY] =
g_signal_new ("ready",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, ready),
signal_accumulator_false_abort, NULL,
NULL,
G_TYPE_BOOLEAN, 0);
/**
* FlatpakTransaction::add-new-remote:
* @object: A #FlatpakTransaction
* @reason: The reason for the new remote is needed
* @from_id: The id of the app/runtime
* @suggested_remote_name: The suggested remote name
* @url: The repo url
*
* As part of the transaction, it is required or recommended
* that a new remote is added, for the reason described in @reason.
* Return %TRUE to add it.
*/
signals[ADD_NEW_REMOTE] =
g_signal_new ("add-new-remote",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (FlatpakTransactionClass, add_new_remote),
g_signal_accumulator_first_wins, NULL,
NULL,
G_TYPE_BOOLEAN, 4, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
}
static void
flatpak_transaction_init (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->last_op_for_ref = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->remote_states = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) flatpak_remote_state_unref);
priv->added_origin_remotes = g_ptr_array_new_with_free_func (g_free);
priv->extra_dependency_dirs = g_ptr_array_new_with_free_func (g_object_unref);
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
FlatpakTransaction *self = FLATPAK_TRANSACTION (initable);
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(FlatpakDir) dir = NULL;
if (priv->installation == NULL)
return flatpak_fail (error, "No installation specified");
dir = flatpak_installation_clone_dir (priv->installation, cancellable, error);
if (dir == NULL)
return FALSE;
priv->dir = g_steal_pointer (&dir);
return TRUE;
}
static void
initable_iface_init (GInitableIface *initable_iface)
{
initable_iface->init = initable_init;
}
/**
* flatpak_transaction_new_for_installation:
* @installation: a #FlatpakInstallation
* @cancellable: (nullable): a #GCancellable
* @error: return location for a #GError
*
* Creates a new #FlatpakTransaction object that can be used to do installation
* and updates of multiple refs, as well as their dependencies, in a single
* operation. Set the options you want on the transaction and add the
* refs you want to install/update, then start the transaction with
* flatpak_transaction_run ().
*
* Returns: (transfer full): a #FlatpakTransaction, or %NULL on failure.
*/
FlatpakTransaction *
flatpak_transaction_new_for_installation (FlatpakInstallation *installation,
GCancellable *cancellable,
GError **error)
{
return g_initable_new (FLATPAK_TYPE_TRANSACTION,
cancellable, error,
"installation", installation,
NULL);
}
void
flatpak_transaction_set_no_pull (FlatpakTransaction *self,
gboolean no_pull)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->no_pull = no_pull;
}
void
flatpak_transaction_set_no_deploy (FlatpakTransaction *self,
gboolean no_deploy)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->no_deploy = no_deploy;
}
void
flatpak_transaction_set_disable_static_deltas (FlatpakTransaction *self,
gboolean disable_static_deltas)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->disable_static_deltas = disable_static_deltas;
}
void
flatpak_transaction_set_disable_prune (FlatpakTransaction *self,
gboolean disable_prune)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->disable_prune = disable_prune;
}
void
flatpak_transaction_set_disable_dependencies (FlatpakTransaction *self,
gboolean disable_dependencies)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->disable_deps = disable_dependencies;
}
void
flatpak_transaction_set_disable_related (FlatpakTransaction *self,
gboolean disable_related)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->disable_related = disable_related;
}
void
flatpak_transaction_set_reinstall (FlatpakTransaction *self,
gboolean reinstall)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->reinstall = reinstall;
}
void
flatpak_transaction_set_force_uninstall (FlatpakTransaction *self,
gboolean force_uninstall)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->force_uninstall = force_uninstall;
}
void
flatpak_transaction_set_default_arch (FlatpakTransaction *self,
const char *default_arch)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_free (priv->default_arch);
priv->default_arch = g_strdup (default_arch);
}
static FlatpakTransactionOperation *
flatpak_transaction_get_last_op_for_ref (FlatpakTransaction *self,
const char *ref)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
FlatpakTransactionOperation *op;
op = g_hash_table_lookup (priv->last_op_for_ref, ref);
return op;
}
static char *
subpaths_to_string (const char **subpaths)
{
GString *s = NULL;
int i;
if (subpaths == NULL)
return g_strdup ("[$old]");
if (*subpaths == 0)
return g_strdup ("[*]");
s = g_string_new ("[");
for (i = 0; subpaths[i] != NULL; i++)
{
if (i != 0)
g_string_append (s, ", ");
g_string_append (s, subpaths[i]);
}
g_string_append (s, "]");
return g_string_free (s, FALSE);
}
static const char *
kind_to_str (FlatpakTransactionOperationType kind)
{
switch ((int) kind)
{
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
return "install";
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
return "update";
case FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE:
return "install/update";
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
return "install bundle";
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
return "uninstall";
case FLATPAK_TRANSACTION_OPERATION_LAST_TYPE:
default:
return "unknown";
}
}
static FlatpakRemoteState *
flatpak_transaction_ensure_remote_state (FlatpakTransaction *self,
FlatpakTransactionOperationType kind,
const char *remote,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
FlatpakRemoteState *state;
/* We don't cache local-only states, as we might later need the same state with non-local state */
if (transaction_is_local_only (self, kind))
return flatpak_dir_get_remote_state_local_only (priv->dir, remote, NULL, error);
state = g_hash_table_lookup (priv->remote_states, remote);
if (state)
return flatpak_remote_state_ref (state);
state = flatpak_dir_get_remote_state_optional (priv->dir, remote, NULL, error);
if (state)
g_hash_table_insert (priv->remote_states, state->remote_name, flatpak_remote_state_ref (state));
return state;
}
static gboolean
kind_compatible (FlatpakTransactionOperationType a,
FlatpakTransactionOperationType b)
{
if (a == b)
return TRUE;
if (a == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE &&
(b == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
b == FLATPAK_TRANSACTION_OPERATION_UPDATE))
return TRUE;
if (b == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE &&
(a == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
a == FLATPAK_TRANSACTION_OPERATION_UPDATE))
return TRUE;
return FALSE;
}
static FlatpakTransactionOperation *
flatpak_transaction_add_op (FlatpakTransaction *self,
const char *remote,
const char *ref,
const char **subpaths,
const char *commit,
GFile *bundle,
FlatpakTransactionOperationType kind)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
FlatpakTransactionOperation *op;
g_autofree char *subpaths_str = NULL;
subpaths_str = subpaths_to_string (subpaths);
g_debug ("Transaction: %s %s:%s%s%s%s",
kind_to_str (kind), remote, ref,
commit != NULL ? "@" : "",
commit != NULL ? commit : "",
subpaths_str);
op = flatpak_transaction_get_last_op_for_ref (self, ref);
if (op != NULL && kind_compatible (kind, op->kind))
{
g_auto(GStrv) old_subpaths = NULL;
old_subpaths = op->subpaths;
op->subpaths = flatpak_subpaths_merge (old_subpaths, (char **) subpaths);
return op;
}
op = flatpak_transaction_operation_new (remote, ref, subpaths, commit, bundle, kind);
g_hash_table_insert (priv->last_op_for_ref, g_strdup (ref), op);
priv->ops = g_list_prepend (priv->ops, op);
return op;
}
static void
run_operation_before (FlatpakTransactionOperation *op,
FlatpakTransactionOperation *before_this,
int prio)
{
if (op == before_this)
return; /* Don't cause unnecessary loops */
op->run_before_ops = g_list_prepend (op->run_before_ops, before_this);
before_this->run_after_count++;
before_this->run_after_prio = MAX (before_this->run_after_prio, prio);
}
static gboolean
add_related (FlatpakTransaction *self,
FlatpakTransactionOperation *op,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(FlatpakRemoteState) state = NULL;
g_autoptr(GPtrArray) related = NULL;
g_autoptr(GError) local_error = NULL;
int i;
if (priv->disable_related)
return TRUE;
state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, error);
if (state == NULL)
return FALSE;
if (op->resolved_metakey == NULL)
{
g_debug ("no resolved metadata for related to %s", op->ref);
return TRUE;
}
if (transaction_is_local_only (self, op->kind))
related = flatpak_dir_find_local_related_for_metadata (priv->dir, op->ref, op->remote, op->resolved_metakey,
NULL, &local_error);
else
related = flatpak_dir_find_remote_related_for_metadata (priv->dir, state, op->ref, op->resolved_metakey,
NULL, &local_error);
if (related == NULL)
{
g_message (_("Warning: Problem looking for related refs: %s"), local_error->message);
return TRUE;
}
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
for (i = 0; i < related->len; i++)
{
FlatpakRelated *rel = g_ptr_array_index (related, i);
FlatpakTransactionOperation *related_op;
if (!rel->delete)
continue;
related_op = flatpak_transaction_add_op (self, op->remote, rel->ref,
NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UNINSTALL);
related_op->non_fatal = TRUE;
related_op->fail_if_op_fails = op;
run_operation_before (op, related_op, 1);
}
}
else /* install or update */
{
for (i = 0; i < related->len; i++)
{
FlatpakRelated *rel = g_ptr_array_index (related, i);
FlatpakTransactionOperation *related_op;
if (!rel->download)
continue;
related_op = flatpak_transaction_add_op (self, op->remote, rel->ref,
(const char **) rel->subpaths,
NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE);
related_op->non_fatal = TRUE;
related_op->fail_if_op_fails = op;
run_operation_before (op, related_op, 1);
}
}
return TRUE;
}
static char *
find_runtime_remote (FlatpakTransaction *self,
const char *app_ref,
const char *runtime_ref,
FlatpakTransactionOperationType source_kind,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_auto(GStrv) remotes = NULL;
const char *app_pref;
const char *runtime_pref;
int res = -1;
app_pref = strchr (app_ref, '/') + 1;
runtime_pref = strchr (runtime_ref, '/') + 1;
if (transaction_is_local_only (self, source_kind))
remotes = flatpak_dir_search_for_local_dependency (priv->dir, runtime_ref, NULL, NULL);
else
remotes = flatpak_dir_search_for_dependency (priv->dir, runtime_ref, NULL, NULL);
if (remotes == NULL || *remotes == NULL)
{
flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_NOT_FOUND,
_("The Application %s requires the %s which was not found"),
app_pref, runtime_pref);
return NULL;
}
/* In the no-puil case, if only one local ref is available, assume that is the one becasue
the user chosed it interactively when pulling */
if (priv->no_pull && g_strv_length (remotes) == 1)
res = 0;
else
g_signal_emit (self, signals[CHOOSE_REMOTE_FOR_REF], 0, app_ref, runtime_ref, remotes, &res);
if (res >= 0 && res < g_strv_length (remotes))
return g_strdup (remotes[res]);
flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_NOT_FOUND,
_("The Application %s requires the %s which is not installed"),
app_pref, runtime_pref);
return NULL;
}
static gboolean
add_deps (FlatpakTransaction *self,
FlatpakTransactionOperation *op,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *runtime_ref = NULL;
g_autofree char *full_runtime_ref = NULL;
g_autofree char *runtime_remote = NULL;
FlatpakTransactionOperation *runtime_op = NULL;
if (!g_str_has_prefix (op->ref, "app/"))
return TRUE;
if (op->resolved_metakey)
runtime_ref = g_key_file_get_string (op->resolved_metakey, "Application", "runtime", NULL);
if (runtime_ref == NULL)
return TRUE;
full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL);
runtime_op = flatpak_transaction_get_last_op_for_ref (self, full_runtime_ref);
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
/* If the runtime this app uses is already to be uninstalled, then this uninstall must happen before
the runtime is installed */
if (runtime_op && op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
run_operation_before (op, runtime_op, 1);
return TRUE;
}
if (priv->disable_deps)
return TRUE;
if (runtime_op == NULL)
{
g_autoptr(GError) local_error = NULL;
if (!ref_is_installed (self, full_runtime_ref, &local_error))
{
if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
runtime_remote = find_runtime_remote (self, op->ref, full_runtime_ref, op->kind, error);
if (runtime_remote == NULL)
return FALSE;
runtime_op = flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE);
}
else
{
/* Update if in same dir */
if (dir_ref_is_installed (priv->dir, full_runtime_ref, &runtime_remote, NULL))
{
g_debug ("Updating dependent runtime %s", full_runtime_ref);
runtime_op = flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, NULL, NULL,
FLATPAK_TRANSACTION_OPERATION_UPDATE);
runtime_op->non_fatal = TRUE;
}
}
}
/* Install/Update the runtime before the app */
if (runtime_op)
{
op->fail_if_op_fails = runtime_op;
run_operation_before (runtime_op, op, 2);
}
return TRUE;
}
static gboolean
flatpak_transaction_add_ref (FlatpakTransaction *self,
const char *remote,
const char *ref,
const char **subpaths,
const char *commit,
FlatpakTransactionOperationType kind,
GFile *bundle,
const char *external_metadata,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *origin = NULL;
const char *pref;
g_autofree char *origin_remote = NULL;
g_autoptr(FlatpakRemoteState) state = NULL;
FlatpakTransactionOperation *op;
if (remote_name_is_file (remote))
{
g_auto(GStrv) parts = NULL;
parts = g_strsplit (ref, "/", -1);
origin_remote = flatpak_dir_create_origin_remote (priv->dir,
remote, /* uri */
parts[1],
"Local repo",
ref,
NULL,
NULL,
NULL, error);
if (origin_remote == NULL)
return FALSE;
g_ptr_array_add (priv->added_origin_remotes, g_strdup (origin_remote));
remote = origin_remote;
}
pref = strchr (ref, '/') + 1;
/* install or update */
if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
{
if (!dir_ref_is_installed (priv->dir, ref, &origin, NULL))
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
_("%s not installed"), pref);
return FALSE;
}
if (flatpak_dir_get_remote_disabled (priv->dir, origin))
{
g_debug (_("Remote %s disabled, ignoring %s update"), origin, pref);
return TRUE;
}
remote = origin;
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
{
if (!priv->reinstall &&
dir_ref_is_installed (priv->dir, ref, &origin, NULL))
{
if (g_strcmp0 (remote, origin) == 0)
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
_("%s is already installed"), pref);
return FALSE;
}
else
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_DIFFERENT_REMOTE,
_("%s is already installed from remote %s"), pref, origin);
return FALSE;
}
}
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
if (!dir_ref_is_installed (priv->dir, ref, &origin, NULL))
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
_("%s not installed"), pref);
return FALSE;
}
remote = origin;
}
/* This should have been passed in or found out above */
g_assert (remote != NULL);
state = flatpak_transaction_ensure_remote_state (self, kind, remote, error);
if (state == NULL)
return FALSE;
op = flatpak_transaction_add_op (self, remote, ref, subpaths, commit, bundle, kind);
if (external_metadata)
op->external_metadata = g_bytes_new (external_metadata, strlen (external_metadata) + 1);
return TRUE;
}
gboolean
flatpak_transaction_add_install (FlatpakTransaction *self,
const char *remote,
const char *ref,
const char **subpaths,
GError **error)
{
const char *all_paths[] = { NULL };
/* If we install with no special args pull all subpaths */
if (subpaths == NULL)
subpaths = all_paths;
return flatpak_transaction_add_ref (self, remote, ref, subpaths, NULL, FLATPAK_TRANSACTION_OPERATION_INSTALL, NULL, NULL, error);
}
gboolean
flatpak_transaction_add_install_bundle (FlatpakTransaction *self,
GFile *file,
GBytes *gpg_data,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
priv->bundles = g_list_append (priv->flatpakrefs, bundle_data_new (file, gpg_data));
return TRUE;
}
gboolean
flatpak_transaction_add_install_flatpakref (FlatpakTransaction *self,
GBytes *flatpakref_data,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
g_autoptr(GError) local_error = NULL;
if (!g_key_file_load_from_data (keyfile, g_bytes_get_data (flatpakref_data, NULL),
g_bytes_get_size (flatpakref_data),
0, &local_error))
return flatpak_fail (error, "Invalid .flatpakref: %s", local_error->message);
priv->flatpakrefs = g_list_append (priv->flatpakrefs, g_steal_pointer (&keyfile));
return TRUE;
}
gboolean
flatpak_transaction_add_update (FlatpakTransaction *self,
const char *ref,
const char **subpaths,
const char *commit,
GError **error)
{
const char *all_paths[] = { NULL };
/* If specify an empty subpath, that means all subpaths */
if (subpaths != NULL && subpaths[0] != NULL && subpaths[0][0] == 0)
subpaths = all_paths;
return flatpak_transaction_add_ref (self, NULL, ref, subpaths, commit, FLATPAK_TRANSACTION_OPERATION_UPDATE, NULL, NULL, error);
}
gboolean
flatpak_transaction_add_uninstall (FlatpakTransaction *self,
const char *ref,
GError **error)
{
return flatpak_transaction_add_ref (self, NULL, ref, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UNINSTALL, NULL, NULL, error);
}
static gboolean
flatpak_transaction_update_metadata (FlatpakTransaction *self,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_auto(GStrv) remotes = NULL;
int i;
GList *l;
g_autoptr(GHashTable) ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
/* Collect all dir+remotes used in this transaction */
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
g_hash_table_add (ht, g_strdup (op->remote));
}
remotes = (char **) g_hash_table_get_keys_as_array (ht, NULL);
g_hash_table_steal_all (ht); /* Move ownership to remotes */
/* Update metadata for said remotes */
for (i = 0; remotes[i] != NULL; i++)
{
char *remote = remotes[i];
g_autoptr(GError) my_error = NULL;
g_debug ("Updating remote metadata for %s", remote);
if (!flatpak_dir_update_remote_configuration (priv->dir, remote, cancellable, &my_error))
g_message (_("Error updating remote metadata for '%s': %s"), remote, my_error->message);
}
/* Reload changed configuration */
if (!flatpak_dir_recreate_repo (priv->dir, cancellable, error))
return FALSE;
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
return TRUE;
}
static void
emit_new_op (FlatpakTransaction *self, FlatpakTransactionOperation *op, FlatpakTransactionProgress *progress)
{
g_signal_emit (self, signals[NEW_OPERATION], 0, op, progress);
}
static void
emit_op_done (FlatpakTransaction *self,
FlatpakTransactionOperation *op,
FlatpakTransactionResult details)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *commit = NULL;
if (priv->no_deploy)
commit = flatpak_dir_read_latest (priv->dir, op->remote, op->ref, NULL, NULL, NULL);
else
{
g_autoptr(GVariant) deploy_data = flatpak_dir_get_deploy_data (priv->dir, op->ref, NULL, NULL);
if (deploy_data)
commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
}
g_signal_emit (self, signals[OPERATION_DONE], 0, op, commit, details);
}
static GBytes *
load_deployed_metadata (FlatpakTransaction *self, const char *ref)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GFile) deploy_dir = NULL;
g_autoptr(GFile) metadata_file = NULL;
g_autofree char *metadata_contents = NULL;
gsize metadata_contents_length;
deploy_dir = flatpak_dir_get_if_deployed (priv->dir, ref, NULL, NULL);
if (deploy_dir == NULL)
return NULL;
metadata_file = g_file_get_child (deploy_dir, "metadata");
if (!g_file_load_contents (metadata_file, NULL, &metadata_contents, &metadata_contents_length, NULL, NULL))
{
g_debug ("No metadata in local deploy of %s", ref);
return NULL;
}
return g_bytes_new_take (g_steal_pointer (&metadata_contents), metadata_contents_length + 1);
}
static void
mark_op_resolved (FlatpakTransactionOperation *op,
const char *commit,
GBytes *metadata,
GBytes *old_metadata)
{
g_debug ("marking op %s:%s resolved to %s", kind_to_str (op->kind), op->ref, commit ? commit : "-");
g_assert (op != NULL);
if (op->kind != FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
g_assert (commit != NULL);
op->resolved = TRUE;
op->resolved_commit = g_strdup (commit);
if (metadata)
{
g_autoptr(GKeyFile) metakey = g_key_file_new ();
if (g_key_file_load_from_bytes (metakey, metadata, G_KEY_FILE_NONE, NULL))
{
op->resolved_metadata = g_bytes_ref (metadata);
op->resolved_metakey = g_steal_pointer (&metakey);
}
else
g_message ("Warning: Failed to parse metadata for %s\n", op->ref);
}
if (old_metadata)
{
g_autoptr(GKeyFile) metakey = g_key_file_new ();
if (g_key_file_load_from_bytes (metakey, old_metadata, G_KEY_FILE_NONE, NULL))
{
op->resolved_old_metadata = g_bytes_ref (old_metadata);
op->resolved_old_metakey = g_steal_pointer (&metakey);
}
else
g_message ("Warning: Failed to parse old metadata for %s\n", op->ref);
}
}
static gboolean
resolve_p2p_ops (FlatpakTransaction *self,
GList *p2p_ops,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GPtrArray) resolves = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_dir_resolve_free);
;
GList *l;
int i;
for (l = p2p_ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakDirResolve *resolve;
g_debug ("resolving %s using p2p", op->ref);
g_assert (op->kind != FLATPAK_TRANSACTION_OPERATION_UNINSTALL);
g_assert (op->kind != FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE);
g_assert (!op->resolved);
resolve = flatpak_dir_resolve_new (op->remote, op->ref, op->commit);
g_ptr_array_add (resolves, resolve);
}
g_ptr_array_add (resolves, NULL);
if (!flatpak_dir_resolve_p2p_refs (priv->dir, (FlatpakDirResolve **) resolves->pdata,
cancellable, error))
return FALSE;
for (i = 0, l = p2p_ops; l != NULL; i++, l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakDirResolve *resolve = g_ptr_array_index (resolves, i);
g_autoptr(GBytes) old_metadata_bytes = NULL;
old_metadata_bytes = load_deployed_metadata (self, op->ref);
mark_op_resolved (op, resolve->resolved_commit, resolve->resolved_metadata, old_metadata_bytes);
}
return TRUE;
}
/* Resolving an operation means figuring out the target commit
checksum and the metadata for that commit, so that we can handle
dependencies from it, and verify versions. */
static gboolean
resolve_ops (FlatpakTransaction *self,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *l;
g_autoptr(GList) collection_id_ops = NULL;
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
g_autoptr(FlatpakRemoteState) state = NULL;
g_autofree char *checksum = NULL;
g_autoptr(GVariant) commit_data = NULL;
g_autoptr(GVariant) commit_metadata = NULL;
g_autoptr(GBytes) metadata_bytes = NULL;
g_autoptr(GBytes) old_metadata_bytes = NULL;
if (op->resolved)
continue;
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
/* We resolve to the deployed metadata, becasue we need it to uninstall related ops */
metadata_bytes = load_deployed_metadata (self, op->ref);
mark_op_resolved (op, NULL, metadata_bytes, NULL);
continue;
}
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
{
g_assert (op->commit != NULL);
mark_op_resolved (op, op->commit, op->external_metadata, NULL);
continue;
}
/* op->kind is INSTALL or UPDATE */
state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, error);
if (state == NULL)
return FALSE;
/* Should we use local state */
if (transaction_is_local_only (self, op->kind))
{
const char *xa_metadata = NULL;
commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, &checksum, NULL, error);
if (commit_data == NULL)
return FALSE;
commit_metadata = g_variant_get_child_value (commit_data, 0);
g_variant_lookup (commit_metadata, "xa.metadata", "&s", &xa_metadata);
if (xa_metadata == NULL)
g_message ("Warning: No xa.metadata in local commit %s ref %s", checksum, op->ref);
else
metadata_bytes = g_bytes_new (xa_metadata, strlen (xa_metadata) + 1);
old_metadata_bytes = load_deployed_metadata (self, op->ref);
mark_op_resolved (op, checksum, metadata_bytes, old_metadata_bytes);
}
else if (state->collection_id == NULL) /* In the non-p2p case we have all the info available in the summary, so use it */
{
const char *metadata = NULL;
g_autoptr(GError) local_error = NULL;
if (op->commit != NULL)
checksum = g_strdup (op->commit);
else if (!flatpak_dir_find_latest_rev (priv->dir, state, op->ref, op->commit, &checksum,
NULL, cancellable, &local_error))
{
/* An unavailable remote summary shouldn't be fatal if we already have the ref */
commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, &checksum, NULL, NULL);
if (commit_data == NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
else
{
g_message (_("Warning: Treating remote fetch error as non-fatal since %s is already installed: %s"),
op->ref, local_error->message);
g_clear_error (&local_error);
}
}
/* TODO: This only gets the metadata for the latest only, we need to handle the case
where the user specified a commit, or p2p doesn't have the latest commit available */
if (!flatpak_remote_state_lookup_cache (state, op->ref, NULL, NULL, &metadata, &local_error))
{
g_message (_("Warning: Can't find %s metadata for dependencies: %s"), op->ref, local_error->message);
g_clear_error (&local_error);
}
else
metadata_bytes = g_bytes_new (metadata, strlen (metadata) + 1);
old_metadata_bytes = load_deployed_metadata (self, op->ref);
mark_op_resolved (op, checksum, metadata_bytes, old_metadata_bytes);
}
else
{
/* This is a (potential) p2p operation, so rather than do these individually we queue them up in an operation later */
collection_id_ops = g_list_prepend (collection_id_ops, op);
}
}
if (collection_id_ops != NULL &&
!resolve_p2p_ops (self, collection_id_ops, cancellable, error))
return FALSE;
return TRUE;
}
static int
compare_op_ref (FlatpakTransactionOperation *a, FlatpakTransactionOperation *b)
{
char *aa = strchr (a->ref, '/');
char *bb = strchr (b->ref, '/');
return g_strcmp0 (aa, bb);
}
static int
compare_op_prio (FlatpakTransactionOperation *a, FlatpakTransactionOperation *b)
{
return b->run_after_prio - a->run_after_prio;
}
static void
sort_ops (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *sorted = NULL;
GList *remaining;
GList *runnable = NULL;
GList *l, *next;
remaining = priv->ops;
priv->ops = NULL;
/* First mark runnable all jobs that depend on nothing.
Note that this seesntially reverses the original list, so these
are in the same order as specified */
for (l = remaining; l != NULL; l = next)
{
FlatpakTransactionOperation *op = l->data;
next = l->next;
if (op->run_after_count == 0)
{
remaining = g_list_remove_link (remaining, l);
runnable = g_list_concat (l, runnable);
}
}
/* If no other order, start in alphabetical ref-order */
runnable = g_list_sort (runnable, (GCompareFunc) compare_op_ref);
while (runnable)
{
GList *run = runnable;
FlatpakTransactionOperation *run_op = run->data;
/* Put the first runnable on the sorted list */
runnable = g_list_remove_link (runnable, run);
sorted = g_list_concat (run, sorted); /* prepends, so reverse at the end */
/* Then greedily run ops that become runnable, in run_after_prio order, so that
related ops are run before depdendencies */
run_op->run_before_ops = g_list_sort (run_op->run_before_ops, (GCompareFunc) compare_op_prio);
for (l = run_op->run_before_ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *after_op = l->data;
after_op->run_after_count--;
if (after_op->run_after_count == 0)
{
GList *after_l = g_list_find (remaining, after_op);
g_assert (after_l != NULL);
remaining = g_list_remove_link (remaining, after_l);
runnable = g_list_concat (after_l, runnable);
}
}
}
if (remaining != NULL)
{
g_warning ("ops remaining after sort, maybe there is a dependency loop?");
sorted = g_list_concat (remaining, sorted);
}
priv->ops = g_list_reverse (sorted);
}
/**
* flatpak_transaction_get_operations:
* @self: a #FlatpakTransaction
*
* Gets the list of operations.
*
* Returns: (transfer full) (element-type FlatpakTransactionOperation): a #GList of operations
*/
GList *
flatpak_transaction_get_operations (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *l;
GList *non_skipped = NULL;
non_skipped = NULL;
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
if (!op->skip)
non_skipped = g_list_prepend (non_skipped, g_object_ref (op));
}
return g_list_reverse (non_skipped);
}
/**
* flatpak_transaction_get_current_operation:
* @self: a #FlatpakTransaction
*
* Gets the current operation.
*
* Returns: (transfer full): the current #FlatpakTransactionOperation
*/
FlatpakTransactionOperation *
flatpak_transaction_get_current_operation (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
return g_object_ref (priv->current_op);
}
/**
* flatpak_transaction_get_installation:
* @self: a #FlatpakTransactionOperation
*
* Gets the installation this transaction was created for.
*
* Returns: (transfer full): a #FlatpakInstallation
*/
FlatpakInstallation *
flatpak_transaction_get_installation (FlatpakTransaction *self)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
return g_object_ref (priv->installation);
}
static GBytes *
download_uri (const char *url,
GError **error)
{
g_autoptr(SoupSession) session = NULL;
g_autoptr(SoupRequest) req = NULL;
g_autoptr(GInputStream) input = NULL;
g_autoptr(GOutputStream) out = NULL;
session = flatpak_create_soup_session (PACKAGE_STRING);
req = soup_session_request (session, url, error);
if (req == NULL)
return NULL;
input = soup_request_send (req, NULL, error);
if (input == NULL)
return NULL;
out = g_memory_output_stream_new_resizable ();
if (!g_output_stream_splice (out,
input,
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET | G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
NULL,
error))
return NULL;
return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out));
}
static gboolean
remote_is_already_configured (FlatpakTransaction *self,
const char *url,
const char *collection_id)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *old_remote = NULL;
int i;
old_remote = flatpak_dir_find_remote_by_uri (priv->dir, url, collection_id);
if (old_remote == NULL)
{
for (i = 0; i < priv->extra_dependency_dirs->len; i++)
{
FlatpakDir *dependency_dir = g_ptr_array_index (priv->extra_dependency_dirs, i);
old_remote = flatpak_dir_find_remote_by_uri (dependency_dir, url, collection_id);
if (old_remote != NULL)
break;
}
}
return old_remote != NULL;
}
static gboolean
handle_suggested_remote_name (FlatpakTransaction *self, GKeyFile *keyfile, GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *suggested_name = NULL;
g_autofree char *name = NULL;
g_autofree char *url = NULL;
g_autofree char *collection_id = NULL;
g_autoptr(GKeyFile) config = NULL;
g_autoptr(GBytes) gpg_key = NULL;
gboolean res;
suggested_name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
FLATPAK_REF_SUGGEST_REMOTE_NAME_KEY, NULL);
if (suggested_name == NULL)
return TRUE;
name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_NAME_KEY, NULL);
if (name == NULL)
return TRUE;
url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_URL_KEY, NULL);
if (url == NULL)
return TRUE;
collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_COLLECTION_ID_KEY, NULL);
if (remote_is_already_configured (self, url, collection_id))
return TRUE;
/* The name is already used, ignore */
if (ostree_repo_remote_get_url (flatpak_dir_get_repo (priv->dir), suggested_name, NULL, NULL))
return TRUE;
res = FALSE;
g_signal_emit (self, signals[ADD_NEW_REMOTE], 0, FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO,
name, suggested_name, url, &res);
if (res)
{
config = flatpak_dir_parse_repofile (priv->dir, suggested_name, TRUE, keyfile, &gpg_key, NULL, error);
if (config == NULL)
return FALSE;
if (!flatpak_dir_modify_remote (priv->dir, suggested_name, config, gpg_key, NULL, error))
return FALSE;
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
return FALSE;
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
}
return TRUE;
}
static gboolean
handle_runtime_repo_deps (FlatpakTransaction *self, const char *id, const char *dep_url, GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autoptr(GBytes) dep_data = NULL;
g_autofree char *runtime_url = NULL;
g_autofree char *new_remote = NULL;
g_autofree char *basename = NULL;
g_autoptr(SoupURI) uri = NULL;
g_auto(GStrv) remotes = NULL;
g_autoptr(GKeyFile) config = NULL;
g_autoptr(GKeyFile) dep_keyfile = g_key_file_new ();
g_autoptr(GBytes) gpg_key = NULL;
g_autofree char *group = NULL;
g_autoptr(GError) local_error = NULL;
g_autofree char *runtime_collection_id = NULL;
char *t;
int i;
gboolean res;
if (priv->disable_deps)
return TRUE;
dep_data = download_uri (dep_url, error);
if (dep_data == NULL)
{
g_prefix_error (error, "Can't load dependent file %s", dep_url);
return FALSE;
}
if (!g_key_file_load_from_data (dep_keyfile,
g_bytes_get_data (dep_data, NULL),
g_bytes_get_size (dep_data),
0, error))
return flatpak_fail (error, "Invalid .flatpakrepo: %s", local_error->message);
uri = soup_uri_new (dep_url);
basename = g_path_get_basename (soup_uri_get_path (uri));
/* Strip suffix */
t = strchr (basename, '.');
if (t != NULL)
*t = 0;
/* Find a free remote name */
remotes = flatpak_dir_list_remotes (priv->dir, NULL, NULL);
i = 0;
do
{
g_clear_pointer (&new_remote, g_free);
if (i == 0)
new_remote = g_strdup (basename);
else
new_remote = g_strdup_printf ("%s-%d", basename, i);
i++;
}
while (remotes != NULL && g_strv_contains ((const char * const *) remotes, new_remote));
config = flatpak_dir_parse_repofile (priv->dir, new_remote, FALSE, dep_keyfile, &gpg_key, NULL, error);
if (config == NULL)
{
g_prefix_error (error, "Can't parse dependent file %s: ", dep_url);
return FALSE;
}
/* See if it already exists */
group = g_strdup_printf ("remote \"%s\"", new_remote);
runtime_url = g_key_file_get_string (config, group, "url", NULL);
g_assert (runtime_url != NULL);
runtime_collection_id = g_key_file_get_string (config, group, "collection-id", NULL);
if (remote_is_already_configured (self, runtime_url, runtime_collection_id))
return TRUE;
res = FALSE;
g_signal_emit (self, signals[ADD_NEW_REMOTE], 0, FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS,
id, new_remote, runtime_url, &res);
if (res)
{
if (!flatpak_dir_modify_remote (priv->dir, new_remote, config, gpg_key, NULL, error))
return FALSE;
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
return FALSE;
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
}
return TRUE;
}
static gboolean
handle_runtime_repo_deps_from_keyfile (FlatpakTransaction *self, GKeyFile *keyfile, GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *dep_url = NULL;
g_autofree char *name = NULL;
if (priv->disable_deps)
return TRUE;
dep_url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
FLATPAK_REF_RUNTIME_REPO_KEY, NULL);
if (dep_url == NULL)
return TRUE;
name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_NAME_KEY, NULL);
if (name == NULL)
return TRUE;
return handle_runtime_repo_deps (self, name, dep_url, error);
}
static gboolean
flatpak_transaction_resolve_flatpakrefs (FlatpakTransaction *self,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *l;
for (l = priv->flatpakrefs; l != NULL; l = l->next)
{
GKeyFile *flatpakref = l->data;
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
/* Handle this before the runtime deps, because they might be the same */
if (!handle_suggested_remote_name (self, flatpakref, error))
return FALSE;
if (!handle_runtime_repo_deps_from_keyfile (self, flatpakref, error))
return FALSE;
if (!flatpak_dir_create_remote_for_ref_file (priv->dir, flatpakref, priv->default_arch,
&remote, NULL, &ref, error))
return FALSE;
/* Need to pick up the new config, in case it was applied in the system helper. */
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
return FALSE;
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
if (!flatpak_transaction_add_install (self, remote, ref, NULL, error))
return FALSE;
}
return TRUE;
}
static gboolean
handle_runtime_repo_deps_from_bundle (FlatpakTransaction *self,
GFile *file,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
g_autofree char *dep_url = NULL;
g_autofree char *ref = NULL;
g_auto(GStrv) ref_parts = NULL;
g_autoptr(GVariant) metadata = NULL;
if (priv->disable_deps)
return TRUE;
metadata = flatpak_bundle_load (file,
NULL,
&ref,
NULL,
&dep_url,
NULL,
NULL,
NULL,
NULL,
NULL);
if (metadata == NULL || dep_url == NULL || ref == NULL)
return TRUE;
ref_parts = g_strsplit (ref, "/", -1);
return handle_runtime_repo_deps (self, ref_parts[1], dep_url, error);
}
static gboolean
flatpak_transaction_resolve_bundles (FlatpakTransaction *self,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *l;
for (l = priv->bundles; l != NULL; l = l->next)
{
BundleData *data = l->data;
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
g_autofree char *commit = NULL;
g_autofree char *metadata = NULL;
gboolean created_remote;
if (!handle_runtime_repo_deps_from_bundle (self, data->file, error))
return FALSE;
if (!flatpak_dir_ensure_repo (priv->dir, cancellable, error))
return FALSE;
remote = flatpak_dir_ensure_bundle_remote (priv->dir, data->file, data->gpg_data,
&ref, &commit, &metadata, &created_remote,
NULL, error);
if (remote == NULL)
return FALSE;
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
return FALSE;
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
if (!flatpak_transaction_add_ref (self, remote, ref, NULL, commit,
FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE, data->file, metadata, error))
return FALSE;
}
return TRUE;
}
gboolean
flatpak_transaction_run (FlatpakTransaction *self,
GCancellable *cancellable,
GError **error)
{
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
GList *l, *next;
gboolean succeeded = TRUE;
gboolean needs_prune = FALSE;
gboolean needs_triggers = FALSE;
g_autoptr(GMainContextPopDefault) main_context = NULL;
gboolean ready_res = FALSE;
int i;
priv->current_op = NULL;
if (!priv->no_pull &&
!flatpak_transaction_update_metadata (self, cancellable, error))
return FALSE;
/* Work around ostree-pull spinning the default main context for the sync calls */
main_context = flatpak_main_context_new_default ();
if (!flatpak_transaction_resolve_flatpakrefs (self, cancellable, error))
return FALSE;
if (!flatpak_transaction_resolve_bundles (self, cancellable, error))
return FALSE;
/* Resolve initial ops */
if (!resolve_ops (self, cancellable, error))
return FALSE;
/* Add all app -> runtime dependencies */
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
if (!add_deps (self, op, error))
return FALSE;
}
/* Resolve new ops */
if (!resolve_ops (self, cancellable, error))
return FALSE;
/* Add all related extensions */
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
if (!add_related (self, op, error))
return FALSE;
}
/* Resolve new ops */
if (!resolve_ops (self, cancellable, error))
return FALSE;
sort_ops (self);
/* Ensure the operation kind is normalized and not no-op */
for (l = priv->ops; l != NULL; l = next)
{
FlatpakTransactionOperation *op = l->data;
next = l->next;
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE)
{
g_autoptr(GVariant) deploy_data = NULL;
if (dir_ref_is_installed (priv->dir, op->ref, NULL, &deploy_data))
{
/* Don't use the remote from related ref on update, always use
the current remote. */
g_free (op->remote);
op->remote = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
op->kind = FLATPAK_TRANSACTION_OPERATION_UPDATE;
}
else
op->kind = FLATPAK_TRANSACTION_OPERATION_INSTALL;
}
/* Skip no-op updates */
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UPDATE &&
!flatpak_dir_needs_update_for_commit_and_subpaths (priv->dir, op->remote, op->ref, op->resolved_commit,
(const char **) op->subpaths))
op->skip = TRUE;
}
g_signal_emit (self, signals[READY], 0, &ready_res);
if (!ready_res)
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED, _("Aborted by user"));
return FALSE;
}
for (l = priv->ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
g_autoptr(GError) local_error = NULL;
gboolean res = TRUE;
const char *pref;
FlatpakTransactionOperationType kind;
g_autoptr(FlatpakRemoteState) state = NULL;
if (op->skip)
continue;
priv->current_op = op;
kind = op->kind;
pref = strchr (op->ref, '/') + 1;
if (op->fail_if_op_fails && (op->fail_if_op_fails->failed) &&
/* Allow installing an app if the runtime failed to update (i.e. is installed) because
* the app should still run, and otherwise you could never install the app until the runtime
* remote is fixed. */
!(op->fail_if_op_fails->kind == FLATPAK_TRANSACTION_OPERATION_UPDATE && g_str_has_prefix (op->ref, "app/")))
{
g_set_error (&local_error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED,
_("Skipping %s due to previous error"), pref);
res = FALSE;
}
else if ((state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, &local_error)) == NULL)
{
res = FALSE;
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
{
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
emit_new_op (self, op, progress);
g_assert (op->resolved_commit != NULL); /* We resolved this before */
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
res = FALSE;
else
res = flatpak_dir_install (priv->dir,
priv->no_pull,
priv->no_deploy,
priv->disable_static_deltas,
priv->reinstall,
state, op->ref, op->resolved_commit,
(const char **) op->subpaths,
progress->ostree_progress,
cancellable, &local_error);
flatpak_transaction_progress_done (progress);
if (res)
{
emit_op_done (self, op, 0);
/* Normally we don't need to prune after install, because it makes no old objects
stale. However if we reinstall, that is not true. */
if (!priv->no_pull && priv->reinstall)
needs_prune = TRUE;
if (g_str_has_prefix (op->ref, "app"))
needs_triggers = TRUE;
}
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
{
g_assert (op->resolved_commit != NULL); /* We resolved this before */
if (flatpak_dir_needs_update_for_commit_and_subpaths (priv->dir, op->remote, op->ref, op->resolved_commit,
(const char **) op->subpaths))
{
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
FlatpakTransactionResult result_details = 0;
emit_new_op (self, op, progress);
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
res = FALSE;
else
res = flatpak_dir_update (priv->dir,
priv->no_pull,
priv->no_deploy,
priv->disable_static_deltas,
op->commit != NULL, /* Allow downgrade if we specify commit */
state, op->ref, op->resolved_commit,
NULL,
(const char **) op->subpaths,
progress->ostree_progress,
cancellable, &local_error);
flatpak_transaction_progress_done (progress);
/* Handle noop-updates */
if (!res && g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
{
res = TRUE;
g_clear_error (&local_error);
result_details |= FLATPAK_TRANSACTION_RESULT_NO_CHANGE;
}
if (res)
{
emit_op_done (self, op, result_details);
if (!priv->no_pull)
needs_prune = TRUE;
if (g_str_has_prefix (op->ref, "app"))
needs_triggers = TRUE;
}
}
else
g_debug ("%s need no update", op->ref);
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
{
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
emit_new_op (self, op, progress);
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
res = FALSE;
else
res = flatpak_dir_install_bundle (priv->dir, op->bundle,
op->remote, NULL,
cancellable, &local_error);
flatpak_transaction_progress_done (progress);
if (res)
{
emit_op_done (self, op, 0);
needs_prune = TRUE;
needs_triggers = TRUE;
}
}
else if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
FlatpakHelperUninstallFlags flags = 0;
if (priv->disable_prune)
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF;
if (priv->force_uninstall)
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
emit_new_op (self, op, progress);
res = flatpak_dir_uninstall (priv->dir, op->ref, flags,
cancellable, &local_error);
flatpak_transaction_progress_done (progress);
if (res)
{
emit_op_done (self, op, 0);
needs_prune = TRUE;
if (g_str_has_prefix (op->ref, "app"))
needs_triggers = TRUE;
}
}
else
g_assert_not_reached ();
if (res)
{
g_autoptr(GVariant) deploy_data = NULL;
deploy_data = flatpak_dir_get_deploy_data (priv->dir, op->ref, NULL, NULL);
if (deploy_data)
{
const char *eol = flatpak_deploy_data_get_eol (deploy_data);
const char *eol_rebase = flatpak_deploy_data_get_eol_rebase (deploy_data);
if (eol || eol_rebase)
g_signal_emit (self, signals[END_OF_LIFED], 0,
op->ref, eol, eol_rebase);
}
}
if (!res)
{
gboolean do_cont = FALSE;
FlatpakTransactionErrorDetails error_details = 0;
op->failed = TRUE;
if (op->non_fatal)
error_details |= FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL;
g_signal_emit (self, signals[OPERATION_ERROR], 0, op,
local_error, error_details,
&do_cont);
if (!do_cont)
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED,
_("Aborted due to failure"));
succeeded = FALSE;
break;
}
}
}
priv->current_op = NULL;
if (needs_triggers)
flatpak_dir_run_triggers (priv->dir, cancellable, NULL);
if (needs_prune && !priv->disable_prune)
flatpak_dir_prune (priv->dir, cancellable, NULL);
for (i = 0; i < priv->added_origin_remotes->len; i++)
flatpak_dir_prune_origin_remote (priv->dir, g_ptr_array_index (priv->added_origin_remotes, i));
return succeeded;
}