diff --git a/Makefile-libostree-defines.am b/Makefile-libostree-defines.am index 6fc4a18a..3fba576b 100644 --- a/Makefile-libostree-defines.am +++ b/Makefile-libostree-defines.am @@ -46,6 +46,7 @@ libostree_public_headers += \ src/libostree/ostree-repo-finder-avahi.h \ src/libostree/ostree-repo-finder-config.h \ src/libostree/ostree-repo-finder-mount.h \ + src/libostree/ostree-repo-finder-override.h \ $(NULL) endif diff --git a/Makefile-libostree.am b/Makefile-libostree.am index ebbe8437..e2ebae3a 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -156,6 +156,7 @@ libostree_experimental_headers = \ src/libostree/ostree-repo-finder-avahi.h \ src/libostree/ostree-repo-finder-config.h \ src/libostree/ostree-repo-finder-mount.h \ + src/libostree/ostree-repo-finder-override.h \ $(NULL) if !ENABLE_EXPERIMENTAL_API libostree_1_la_SOURCES += $(libostree_experimental_headers) @@ -167,6 +168,7 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-repo-finder-avahi.c \ src/libostree/ostree-repo-finder-config.c \ src/libostree/ostree-repo-finder-mount.c \ + src/libostree/ostree-repo-finder-override.c \ $(NULL) if USE_AVAHI diff --git a/apidoc/ostree-experimental-sections.txt b/apidoc/ostree-experimental-sections.txt index c43d11e1..309d07fb 100644 --- a/apidoc/ostree-experimental-sections.txt +++ b/apidoc/ostree-experimental-sections.txt @@ -76,6 +76,15 @@ ostree_repo_finder_mount_new ostree_repo_finder_mount_get_type +
+ostree-repo-finder-override +OstreeRepoFinderOverride +ostree_repo_finder_override_new +ostree_repo_finder_override_add_uri + +ostree_repo_finder_override_get_type +
+
ostree-misc-experimental ostree_repo_get_collection_id diff --git a/src/libostree/libostree-experimental.sym b/src/libostree/libostree-experimental.sym index 87f274da..cbe373cf 100644 --- a/src/libostree/libostree-experimental.sym +++ b/src/libostree/libostree-experimental.sym @@ -82,3 +82,10 @@ LIBOSTREE_2017.12_EXPERIMENTAL { global: ostree_repo_resolve_collection_ref; } LIBOSTREE_2017.8_EXPERIMENTAL; + +LIBOSTREE_2017.13_EXPERIMENTAL { +global: + ostree_repo_finder_override_add_uri; + ostree_repo_finder_override_get_type; + ostree_repo_finder_override_new; +} LIBOSTREE_2017.12_EXPERIMENTAL; diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h index 7228c0b2..c8e8a857 100644 --- a/src/libostree/ostree-autocleanups.h +++ b/src/libostree/ostree-autocleanups.h @@ -66,6 +66,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderAvahi, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderOverride, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index 32fe7e51..08809e4a 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -223,6 +223,9 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) #include "ostree-repo-finder-mount.h" G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) + +#include "ostree-repo-finder-override.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderOverride, g_object_unref) #endif G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-override.c b/src/libostree/ostree-repo-finder-override.c new file mode 100644 index 00000000..e5bb80d7 --- /dev/null +++ b/src/libostree/ostree-repo-finder-override.c @@ -0,0 +1,322 @@ +/* + * Copyright © 2017 Endless Mobile, Inc. + * + * This library 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-remote-private.h" +#include "ostree-repo.h" +#include "ostree-repo-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-override.h" + +/** + * SECTION:ostree-repo-finder-override + * @title: OstreeRepoFinderOverride + * @short_description: Finds remote repositories from a list of repository URIs + * @stability: Unstable + * @include: libostree/ostree-repo-finder-override.h + * + * #OstreeRepoFinderOverride is an implementation of #OstreeRepoFinder which + * looks refs up in a list of remotes given by their URI, and returns the URIs + * which contain the refs. Duplicate remote URIs are combined into a single + * #OstreeRepoFinderResult which lists multiple refs. + * + * Each result is given an #OstreeRepoFinderResult.priority value of 20, which + * ranks its results above those from the other default #OstreeRepoFinder + * implementations. + * + * Results can only be returned for a ref if a remote and keyring are configured + * locally for the collection ID of that ref, otherwise there would be no keys + * available to verify signatures on commits for that ref. + * + * This is intended to be used for user-provided overrides and testing software + * which uses #OstreeRepoFinder. For production use, #OstreeRepoFinderConfig is + * recommended instead. + * + * Since: 2017.13 + */ + +static void ostree_repo_finder_override_iface_init (OstreeRepoFinderInterface *iface); + +struct _OstreeRepoFinderOverride +{ + GObject parent_instance; + + GPtrArray *override_uris; /* (owned) (element-type utf8) */ +}; + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderOverride, ostree_repo_finder_override, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_override_iface_init)) + +static gint +results_compare_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +/* This must return a valid remote name (suitable for use in a refspec). */ +static gchar * +uri_and_keyring_to_name (const gchar *uri, + const gchar *keyring) +{ + g_autofree gchar *escaped_uri = g_uri_escape_string (uri, NULL, FALSE); + g_autofree gchar *escaped_keyring = g_uri_escape_string (keyring, NULL, FALSE); + + /* FIXME: Need a better separator than `_`, since it’s not escaped in the input. */ + g_autofree gchar *out = g_strdup_printf ("%s_%s", escaped_uri, escaped_keyring); + + for (gsize i = 0; out[i] != '\0'; i++) + { + if (out[i] == '%') + out[i] = '_'; + } + + g_return_val_if_fail (ostree_validate_remote_name (out, NULL), NULL); + + return g_steal_pointer (&out); +} + +/* Version of ostree_repo_remote_list_collection_refs() which takes an + * #OstreeRemote. */ +static gboolean +repo_remote_list_collection_refs (OstreeRepo *repo, + const gchar *remote_uri, + GHashTable **out_all_refs, + GCancellable *cancellable, + GError **error) +{ + g_autofree gchar *name = uri_and_keyring_to_name (remote_uri, ""); + g_autoptr(OstreeRemote) remote = ostree_remote_new (name); + g_key_file_set_string (remote->options, remote->group, "url", remote_uri); + + gboolean remote_already_existed = _ostree_repo_add_remote (repo, remote); + gboolean success = ostree_repo_remote_list_collection_refs (repo, + remote->name, + out_all_refs, + cancellable, + error); + + if (!remote_already_existed) + _ostree_repo_remove_remote (repo, remote); + + return success; +} + +static void +ostree_repo_finder_override_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OstreeRepoFinderOverride *self = OSTREE_REPO_FINDER_OVERRIDE (finder); + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) results = NULL; + const gint priority = 20; /* arbitrarily chosen; higher priority than the others */ + gsize i, j; + g_autoptr(GHashTable) repo_remote_to_refs = NULL; /* (element-type OstreeRemote GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + const gchar *remote_uri; + OstreeRemote *remote; + + task = g_task_new (finder, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_override_resolve_async); + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + repo_remote_to_refs = g_hash_table_new_full (g_direct_hash, g_direct_equal, + (GDestroyNotify) ostree_remote_unref, + (GDestroyNotify) g_hash_table_unref); + + g_debug ("%s: Checking %u overrides", G_STRFUNC, self->override_uris->len); + + for (i = 0; i < self->override_uris->len; i++) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(GHashTable) remote_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + const gchar *checksum; + gboolean resolved_a_ref = FALSE; + + remote_uri = self->override_uris->pdata[i]; + + if (!repo_remote_list_collection_refs (parent_repo, remote_uri, + &remote_refs, cancellable, + &local_error)) + { + g_debug ("Ignoring remote ‘%s’ due to error loading its refs: %s", + remote_uri, local_error->message); + g_clear_error (&local_error); + continue; + } + + for (j = 0; refs[j] != NULL; j++) + { + g_autoptr(OstreeRemote) keyring_remote = NULL; + + /* Look up the GPG keyring for this ref. */ + keyring_remote = ostree_repo_resolve_keyring_for_collection (parent_repo, + refs[j]->collection_id, + cancellable, &local_error); + + if (keyring_remote == NULL) + { + g_debug ("Ignoring ref (%s, %s) due to missing keyring: %s", + refs[j]->collection_id, refs[j]->ref_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + if (g_hash_table_lookup_extended (remote_refs, refs[j], NULL, (gpointer *) &checksum)) + { + g_autoptr(OstreeRemote) remote = NULL; + + /* The requested ref is listed in the refs for this remote. Add + * the remote to the results, and the ref to its + * @supported_ref_to_checksum. */ + g_debug ("Resolved ref (%s, %s) to remote ‘%s’.", + refs[j]->collection_id, refs[j]->ref_name, remote_uri); + resolved_a_ref = TRUE; + + /* Build an #OstreeRemote. Use the escaped URI, since remote->name + * is used in file paths, so needs to not contain special characters. */ + g_autofree gchar *name = uri_and_keyring_to_name (remote_uri, keyring_remote->name); + remote = ostree_remote_new_dynamic (name, keyring_remote->name); + + /* gpg-verify-summary is false since we use the unsigned summary file support. */ + g_key_file_set_string (remote->options, remote->group, "url", remote_uri); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify", TRUE); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify-summary", FALSE); + + supported_ref_to_checksum = g_hash_table_lookup (repo_remote_to_refs, remote); + + if (supported_ref_to_checksum == NULL) + { + supported_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + NULL, g_free); + g_hash_table_insert (repo_remote_to_refs, ostree_remote_ref (remote), supported_ref_to_checksum /* transfer */); + } + + g_hash_table_insert (supported_ref_to_checksum, + (gpointer) refs[j], g_strdup (checksum)); + } + } + + if (!resolved_a_ref) + g_debug ("Ignoring remote ‘%s’ due to it not advertising any of the requested refs.", + remote_uri); + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_remote_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &remote, (gpointer *) &supported_ref_to_checksum)) + g_ptr_array_add (results, ostree_repo_finder_result_new (remote, finder, priority, supported_ref_to_checksum, 0)); + + g_ptr_array_sort (results, results_compare_cb); + + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); +} + +static GPtrArray * +ostree_repo_finder_override_resolve_finish (OstreeRepoFinder *finder, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, finder), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_override_init (OstreeRepoFinderOverride *self) +{ + self->override_uris = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); +} + +static void +ostree_repo_finder_override_finalize (GObject *object) +{ + OstreeRepoFinderOverride *self = OSTREE_REPO_FINDER_OVERRIDE (object); + + g_clear_pointer (&self->override_uris, g_ptr_array_unref); + + G_OBJECT_CLASS (ostree_repo_finder_override_parent_class)->finalize (object); +} + +static void +ostree_repo_finder_override_class_init (OstreeRepoFinderOverrideClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = ostree_repo_finder_override_finalize; +} + +static void +ostree_repo_finder_override_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_override_resolve_async; + iface->resolve_finish = ostree_repo_finder_override_resolve_finish; +} + +/** + * ostree_repo_finder_override_new: + * + * Create a new #OstreeRepoFinderOverride. + * + * Returns: (transfer full): a new #OstreeRepoFinderOverride + * Since: 2017.13 + */ +OstreeRepoFinderOverride * +ostree_repo_finder_override_new (void) +{ + return g_object_new (OSTREE_TYPE_REPO_FINDER_OVERRIDE, NULL); +} + +/** + * ostree_repo_finder_override_add_uri: + * @uri: URI to add to the repo finder + * + * Add the given @uri to the set of URIs which the repo finder will search for + * matching refs when ostree_repo_finder_resolve_async() is called on it. + * + * Since: 2017.13 + */ +void +ostree_repo_finder_override_add_uri (OstreeRepoFinderOverride *self, + const gchar *uri) +{ + g_return_if_fail (OSTREE_IS_REPO_FINDER_OVERRIDE (self)); + g_return_if_fail (uri != NULL); + + g_ptr_array_add (self->override_uris, g_strdup (uri)); +} diff --git a/src/libostree/ostree-repo-finder-override.h b/src/libostree/ostree-repo-finder-override.h new file mode 100644 index 00000000..d28d3bd8 --- /dev/null +++ b/src/libostree/ostree-repo-finder-override.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2017 Endless Mobile, Inc. + * + * This library 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-repo-finder.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER_OVERRIDE (ostree_repo_finder_override_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderOverride, ostree_repo_finder_override, OSTREE, REPO_FINDER_OVERRIDE, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_override_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderOverride OstreeRepoFinderOverride; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderOverrideClass; + +static inline OstreeRepoFinderOverride *OSTREE_REPO_FINDER_OVERRIDE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_override_get_type (), OstreeRepoFinderOverride); } +static inline gboolean OSTREE_IS_REPO_FINDER_OVERRIDE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_override_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderOverride *ostree_repo_finder_override_new (void); + +_OSTREE_PUBLIC +void ostree_repo_finder_override_add_uri (OstreeRepoFinderOverride *self, + const gchar *uri); + +G_END_DECLS diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h index bd748e68..dd0e052b 100644 --- a/src/libostree/ostree.h +++ b/src/libostree/ostree.h @@ -40,6 +40,7 @@ #include #include #include +#include #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #include