lib/repo-finder: Add OstreeRepoFinderOverride
authorPhilip Withnall <withnall@endlessm.com>
Mon, 16 Oct 2017 15:18:03 +0000 (16:18 +0100)
committerAtomic Bot <atomic-devel@projectatomic.io>
Thu, 19 Oct 2017 19:11:58 +0000 (19:11 +0000)
This is another OstreeRepoFinder implementation; it returns results from
a given set of URIs. It’s designed to be used for implementing user
overrides to other repo-finders, or for implementing unit tests.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
Closes: #1281
Approved by: mwleeds

Makefile-libostree-defines.am
Makefile-libostree.am
apidoc/ostree-experimental-sections.txt
src/libostree/libostree-experimental.sym
src/libostree/ostree-autocleanups.h
src/libostree/ostree-core-private.h
src/libostree/ostree-repo-finder-override.c [new file with mode: 0644]
src/libostree/ostree-repo-finder-override.h [new file with mode: 0644]
src/libostree/ostree.h

index 6fc4a18a453f5a79d999d94ebad65554f3c62baf..3fba576b03423f656a7d1953497229877b90343a 100644 (file)
@@ -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
 
index ebbe8437cd5dbc7cd94530fa306c8778d54d356d..e2ebae3a809a9c00bf4611872335ce5b642efc2a 100644 (file)
@@ -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
index c43d11e17c8cc0d4fb87e7e395dd323b37394c75..309d07fb3af78f956dbf3fa46b2fc8830c5c42f8 100644 (file)
@@ -76,6 +76,15 @@ ostree_repo_finder_mount_new
 ostree_repo_finder_mount_get_type
 </SECTION>
 
+<SECTION>
+<FILE>ostree-repo-finder-override</FILE>
+OstreeRepoFinderOverride
+ostree_repo_finder_override_new
+ostree_repo_finder_override_add_uri
+<SUBSECTION Standard>
+ostree_repo_finder_override_get_type
+</SECTION>
+
 <SECTION>
 <FILE>ostree-misc-experimental</FILE>
 ostree_repo_get_collection_id
index 87f274dae9d7f2e576ecc71a487f581585c2a69e..cbe373cf6688ee0cf5420da7fb9ff05476a1d888 100644 (file)
@@ -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;
index 7228c0b2e66970ce1092c27cd088e9389e54d7a3..c8e8a8572dc73483fb9951edbd4c094e5b8b22e5 100644 (file)
@@ -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 */
index 32fe7e51c8bf6f20c803a7c409914f683b4dc4ef..08809e4a26799033e1064082097a989fa63bc1b0 100644 (file)
@@ -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 (file)
index 0000000..e5bb80d
--- /dev/null
@@ -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 <withnall@endlessm.com>
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <libglnx.h>
+
+#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 (file)
index 0000000..d28d3bd
--- /dev/null
@@ -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 <withnall@endlessm.com>
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#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
index bd748e680cbad566671c2a15afa36abd7d6546ec..dd0e052b29b89298cff7ff3f13e55a62ff5562a7 100644 (file)
@@ -40,6 +40,7 @@
 #include <ostree-repo-finder-avahi.h>
 #include <ostree-repo-finder-config.h>
 #include <ostree-repo-finder-mount.h>
+#include <ostree-repo-finder-override.h>
 #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */
 
 #include <ostree-autocleanups.h>