Files
flatpak/app/flatpak-builtins-install.c
Joaquim Rocha 516e249e50 lib/installation: Use "collection refs" when listing refs
When listing refs installed or from a remote, only the refs matching the
main collection-id were being returned. However, it is very important to
have access to all refs, independently from their collection-id,
especially when trying to list remotes coming from a USB repository.

These changes add the mentioned refs and update the places that use
this list, both in the lib and in the CLI.

For the implementation to become easier, we introduce also a
FlatpakCollectionRef, that should be replaced by OstreeCollectionRef
once the latter becomes part of the general API (currently it is in the
experimental one).

Closes: #1587
Approved by: mwleeds
2018-04-19 20:59:34 +00:00

611 lines
19 KiB
C

/*
* Copyright © 2014 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.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 <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gio/gunixinputstream.h>
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-builtins-utils.h"
#include "flatpak-transaction.h"
#include "flatpak-utils.h"
#include "lib/flatpak-error.h"
#include "flatpak-chain-input-stream.h"
static char *opt_arch;
static char **opt_gpg_file;
static char **opt_subpaths;
static gboolean opt_no_pull;
static gboolean opt_no_deploy;
static gboolean opt_no_related;
static gboolean opt_no_deps;
static gboolean opt_no_static_deltas;
static gboolean opt_runtime;
static gboolean opt_app;
static gboolean opt_bundle;
static gboolean opt_from;
static gboolean opt_yes;
static gboolean opt_reinstall;
static GOptionEntry options[] = {
{ "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to install for"), N_("ARCH") },
{ "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only install from local cache"), NULL },
{ "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL },
{ "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't install related refs"), NULL },
{ "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL },
{ "no-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_no_static_deltas, N_("Don't use static deltas"), NULL },
{ "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL },
{ "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL },
{ "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Assume LOCATION is a .flatpak single-file bundle"), NULL },
{ "from", 0, 0, G_OPTION_ARG_NONE, &opt_from, N_("Assume LOCATION is a .flatpakref application description"), NULL },
{ "gpg-file", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_gpg_file, N_("Check bundle signatures with GPG key from FILE (- for stdin)"), N_("FILE") },
{ "subpath", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_subpaths, N_("Only install this subpath"), N_("PATH") },
{ "assumeyes", 'y', 0, G_OPTION_ARG_NONE, &opt_yes, N_("Automatically answer yes for all questions"), NULL },
{ "reinstall", 0, 0, G_OPTION_ARG_NONE, &opt_reinstall, N_("Uninstall first if already installed"), NULL },
{ NULL }
};
static GBytes *
read_gpg_data (GCancellable *cancellable,
GError **error)
{
g_autoptr(GInputStream) source_stream = NULL;
guint n_keyrings = 0;
g_autoptr(GPtrArray) streams = NULL;
if (opt_gpg_file != NULL)
n_keyrings = g_strv_length (opt_gpg_file);
guint ii;
streams = g_ptr_array_new_with_free_func (g_object_unref);
for (ii = 0; ii < n_keyrings; ii++)
{
GInputStream *input_stream = NULL;
if (strcmp (opt_gpg_file[ii], "-") == 0)
{
input_stream = g_unix_input_stream_new (STDIN_FILENO, FALSE);
}
else
{
g_autoptr(GFile) file = g_file_new_for_commandline_arg (opt_gpg_file[ii]);
input_stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
if (input_stream == NULL)
return NULL;
}
/* Takes ownership. */
g_ptr_array_add (streams, input_stream);
}
/* Chain together all the --keyring options as one long stream. */
source_stream = (GInputStream *) flatpak_chain_input_stream_new (streams);
return flatpak_read_stream (source_stream, FALSE, error);
}
static gboolean
remote_is_already_configured (FlatpakDir *dir,
const char *url,
const char *collection_id)
{
g_autofree char *old_remote = NULL;
old_remote = flatpak_dir_find_remote_by_uri (dir, url, collection_id);
if (old_remote == NULL && flatpak_dir_is_user (dir))
{
g_autoptr(GPtrArray) system_dirs = NULL;
int i;
system_dirs = flatpak_dir_get_system_list (NULL, NULL);
if (system_dirs == NULL)
return FALSE;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
old_remote = flatpak_dir_find_remote_by_uri (system_dir, url, collection_id);
if (old_remote != NULL)
break;
}
}
return old_remote != NULL;
}
static gboolean
handle_runtime_repo_deps (FlatpakDir *dir, const char *dep_url, GError **error)
{
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(GBytes) gpg_key = NULL;
g_autofree char *group = NULL;
g_autofree char *runtime_collection_id = NULL;
char *t;
int i;
if (opt_no_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;
}
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 (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 (dir, new_remote, FALSE, dep_data, &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);
#ifdef FLATPAK_ENABLE_P2P
runtime_collection_id = g_key_file_get_string (config, group, "collection-id", NULL);
#endif /* FLATPAK_ENABLE_P2P */
if (remote_is_already_configured (dir, runtime_url, runtime_collection_id))
return TRUE;
if (opt_yes ||
flatpak_yes_no_prompt (_("This application depends on runtimes from:\n %s\nConfigure this as new remote '%s'"),
runtime_url, new_remote))
{
if (opt_yes)
g_print (_("Configuring %s as new remote '%s'"), runtime_url, new_remote);
if (!flatpak_dir_modify_remote (dir, new_remote, config, gpg_key, NULL, error))
return FALSE;
if (!flatpak_dir_recreate_repo (dir, NULL, error))
return FALSE;
}
return TRUE;
}
static gboolean
handle_runtime_repo_deps_from_bundle (FlatpakDir *dir, GFile *file, GError **error)
{
g_autofree char *dep_url = NULL;
g_autoptr(GVariant) metadata = NULL;
if (opt_no_deps)
return TRUE;
metadata = flatpak_bundle_load (file, NULL,
NULL,
NULL,
&dep_url,
NULL,
NULL,
NULL,
NULL,
NULL);
if (metadata == NULL || dep_url == NULL)
return TRUE;
return handle_runtime_repo_deps (dir, dep_url, error);
}
static gboolean
install_bundle (FlatpakDir *dir,
GOptionContext *context,
int argc, char **argv,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) file = NULL;
const char *filename;
g_autoptr(GBytes) gpg_data = NULL;
g_autoptr(FlatpakTransaction) transaction = NULL;
if (argc < 2)
return usage_error (context, _("Bundle filename must be specified"), error);
if (argc > 2)
return usage_error (context, _("Too many arguments"), error);
filename = argv[1];
file = g_file_new_for_commandline_arg (filename);
if (!g_file_is_native (file))
return flatpak_fail (error, _("Remote bundles are not supported"));
if (opt_gpg_file != NULL)
{
/* Override gpg_data from file */
gpg_data = read_gpg_data (cancellable, error);
if (gpg_data == NULL)
return FALSE;
}
if (!handle_runtime_repo_deps_from_bundle (dir, file, error))
return FALSE;
if (!flatpak_dir_ensure_repo (dir, cancellable, error))
return FALSE;
transaction = flatpak_transaction_new (dir, opt_yes, opt_no_pull, opt_no_deploy,
opt_no_static_deltas, !opt_no_deps, !opt_no_related, opt_reinstall);
if (!flatpak_transaction_add_install_bundle (transaction, file, gpg_data, error))
return FALSE;
if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
return FALSE;
return TRUE;
}
static gboolean
handle_suggested_remote_name (FlatpakDir *dir, GBytes *data, GError **error)
{
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
g_autofree char *suggested_name = NULL;
g_autofree char *url = NULL;
g_autofree char *collection_id = NULL;
g_autoptr(GKeyFile) config = NULL;
g_autoptr(GBytes) gpg_key = NULL;
if (!g_key_file_load_from_data (keyfile, g_bytes_get_data (data, NULL), g_bytes_get_size (data),
0, error))
return FALSE;
suggested_name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
FLATPAK_REF_SUGGEST_REMOTE_NAME_KEY, NULL);
if (suggested_name == NULL)
return TRUE;
url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
FLATPAK_REF_URL_KEY, NULL);
if (url == NULL)
return TRUE;
#ifdef FLATPAK_ENABLE_P2P
collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_COLLECTION_ID_KEY, NULL);
#endif /* FLATPAK_ENABLE_P2P */
if (remote_is_already_configured (dir, url, collection_id))
return TRUE;
/* The name is already used, ignore */
if (ostree_repo_remote_get_url (flatpak_dir_get_repo (dir), suggested_name, NULL, NULL))
return TRUE;
if (opt_yes ||
flatpak_yes_no_prompt (_("The remote '%s', at location %s contains additional applications.\nShould the remote be kept for future installations?"),
suggested_name, url))
{
if (opt_yes)
g_print (_("Configuring %s as new remote '%s'"), url, suggested_name);
config = flatpak_dir_parse_repofile (dir, suggested_name, TRUE, data, &gpg_key, NULL, error);
if (config == NULL)
return FALSE;
if (!flatpak_dir_modify_remote (dir, suggested_name, config, gpg_key, NULL, error))
return FALSE;
if (!flatpak_dir_recreate_repo (dir, NULL, error))
return FALSE;
}
return TRUE;
}
static gboolean
handle_runtime_repo_deps_from_keyfile (FlatpakDir *dir, GBytes *data, GError **error)
{
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
g_autofree char *dep_url = NULL;
if (opt_no_deps)
return TRUE;
if (!g_key_file_load_from_data (keyfile, g_bytes_get_data (data, NULL), g_bytes_get_size (data),
0, error))
return FALSE;
dep_url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
FLATPAK_REF_RUNTIME_REPO_KEY, NULL);
if (dep_url == NULL)
return TRUE;
return handle_runtime_repo_deps (dir, dep_url, error);
}
static gboolean
install_from (FlatpakDir *dir,
GOptionContext *context,
int argc, char **argv,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) file = NULL;
g_autoptr(GBytes) file_data = NULL;
g_autofree char *data = NULL;
gsize data_len;
const char *filename;
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
const char *slash;
FlatpakDir *clone;
g_autoptr(FlatpakTransaction) transaction = NULL;
if (argc < 2)
return usage_error (context, _("Filename or uri must be specified"), error);
if (argc > 2)
return usage_error (context, _("Too many arguments"), error);
filename = argv[1];
if (g_str_has_prefix (filename, "http:") ||
g_str_has_prefix (filename, "https:"))
{
file_data = download_uri (filename, error);
if (file_data == NULL)
{
g_prefix_error (error, "Can't load uri %s: ", filename);
return FALSE;
}
}
else
{
file = g_file_new_for_commandline_arg (filename);
if (!g_file_load_contents (file, cancellable, &data, &data_len, NULL, error))
return FALSE;
file_data = g_bytes_new_take (g_steal_pointer (&data), data_len);
}
/* Handle this before the runtime deps, because they might be the same */
if (!handle_suggested_remote_name (dir, file_data, error))
return FALSE;
if (!handle_runtime_repo_deps_from_keyfile (dir, file_data, error))
return FALSE;
if (!flatpak_dir_create_remote_for_ref_file (dir, file_data, opt_arch, &remote, NULL, &ref, error))
return FALSE;
/* Need to pick up the new config, in case it was applied in the system helper. */
clone = flatpak_dir_clone (dir);
if (!flatpak_dir_ensure_repo (clone, cancellable, error))
return FALSE;
slash = strchr (ref, '/');
g_print (_("Installing: %s\n"), slash + 1);
transaction = flatpak_transaction_new (clone, opt_yes, opt_no_pull, opt_no_deploy,
opt_no_static_deltas, !opt_no_deps, !opt_no_related, opt_reinstall);
if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error))
return FALSE;
if (!flatpak_transaction_update_metadata (transaction, FALSE, cancellable, error))
return FALSE;
if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
return FALSE;
return TRUE;
}
gboolean
flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
FlatpakDir *dir;
const char *remote;
g_autofree char *remote_url = NULL;
char **prefs = NULL;
int i, n_prefs;
g_autofree char *target_branch = NULL;
g_autofree char *default_branch = NULL;
FlatpakKinds kinds;
g_autoptr(FlatpakTransaction) transaction = NULL;
context = g_option_context_new (_("LOCATION/REMOTE [REF...] - Install applications or runtimes"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv,
FLATPAK_BUILTIN_FLAG_ONE_DIR,
&dirs, cancellable, error))
return FALSE;
dir = g_ptr_array_index (dirs, 0);
if (!opt_bundle && !opt_from && argc >= 2)
{
if (flatpak_file_arg_has_suffix (argv[1], ".flatpakref"))
opt_from = TRUE;
if (flatpak_file_arg_has_suffix (argv[1], ".flatpak"))
opt_bundle = TRUE;
}
if (opt_bundle)
return install_bundle (dir, context, argc, argv, cancellable, error);
if (opt_from)
return install_from (dir, context, argc, argv, cancellable, error);
if (argc < 3)
return usage_error (context, _("REMOTE and REF must be specified"), error);
if (g_path_is_absolute (argv[1]) ||
g_str_has_prefix (argv[1], "./"))
{
g_autoptr(GFile) remote_file = g_file_new_for_commandline_arg (argv[1]);
remote_url = g_file_get_uri (remote_file);
remote = remote_url;
}
else
remote = argv[1];
prefs = &argv[2];
n_prefs = argc - 2;
/* Backwards compat for old "REMOTE NAME [BRANCH]" argument version */
if (argc == 4 && looks_like_branch (argv[3]))
{
target_branch = g_strdup (argv[3]);
n_prefs = 1;
}
default_branch = flatpak_dir_get_remote_default_branch (dir, remote);
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
transaction = flatpak_transaction_new (dir, opt_yes, opt_no_pull, opt_no_deploy,
opt_no_static_deltas, !opt_no_deps, !opt_no_related, opt_reinstall);
for (i = 0; i < n_prefs; i++)
{
const char *pref = prefs[i];
FlatpakKinds matched_kinds;
g_autofree char *id = NULL;
g_autofree char *arch = NULL;
g_autofree char *branch = NULL;
FlatpakKinds kind;
g_autofree char *ref = NULL;
if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, target_branch,
&matched_kinds, &id, &arch, &branch, error))
return FALSE;
if (opt_no_pull)
ref = flatpak_dir_find_local_ref (dir, remote, id, branch, default_branch, arch,
matched_kinds, &kind, cancellable, error);
else
ref = flatpak_dir_find_remote_ref (dir, remote, id, branch, default_branch, arch,
matched_kinds, &kind, cancellable, error);
if (ref == NULL)
return FALSE;
if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error))
return FALSE;
}
if (!opt_no_pull &&
!flatpak_transaction_update_metadata (transaction, FALSE, cancellable, error))
return FALSE;
if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
return FALSE;
return TRUE;
}
gboolean
flatpak_complete_install (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
FlatpakDir *dir;
FlatpakKinds kinds;
int i;
context = g_option_context_new ("");
if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
FLATPAK_BUILTIN_FLAG_ONE_DIR, &dirs, NULL, NULL))
return FALSE;
dir = g_ptr_array_index (dirs, 0);
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
switch (completion->argc)
{
case 0:
case 1: /* LOCATION/REMOTE */
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, options);
flatpak_complete_options (completion, user_entries);
flatpak_complete_file (completion, "__FLATPAK_BUNDLE_OR_REF_FILE");
{
g_auto(GStrv) remotes = flatpak_dir_list_remotes (dir, NULL, NULL);
if (remotes != NULL)
{
for (i = 0; remotes[i] != NULL; i++)
flatpak_complete_word (completion, "%s ", remotes[i]);
}
}
break;
default: /* REF */
flatpak_complete_partial_ref (completion, kinds, opt_arch, dir, completion->argv[1]);
break;
}
return TRUE;
}