diff options
-rw-r--r-- | Makefile-libostree-defines.am | 1 | ||||
-rw-r--r-- | Makefile-libostree.am | 4 | ||||
-rw-r--r-- | Makefile-tests.am | 7 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | apidoc/ostree-experimental-sections.txt | 8 | ||||
-rw-r--r-- | src/libostree/libostree-experimental.sym | 2 | ||||
-rw-r--r-- | src/libostree/ostree-autocleanups.h | 1 | ||||
-rw-r--r-- | src/libostree/ostree-core-private.h | 3 | ||||
-rw-r--r-- | src/libostree/ostree-repo-finder-mount.c | 547 | ||||
-rw-r--r-- | src/libostree/ostree-repo-finder-mount.h | 55 | ||||
-rw-r--r-- | src/libostree/ostree-repo-pull.c | 4 | ||||
-rw-r--r-- | src/libostree/ostree.h | 1 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/test-mock-gio.c | 400 | ||||
-rw-r--r-- | tests/test-mock-gio.h | 127 | ||||
-rw-r--r-- | tests/test-repo-finder-mount.c | 497 |
16 files changed, 1657 insertions, 3 deletions
diff --git a/Makefile-libostree-defines.am b/Makefile-libostree-defines.am index ac5eaa00..7586f7b9 100644 --- a/Makefile-libostree-defines.am +++ b/Makefile-libostree-defines.am @@ -44,6 +44,7 @@ libostree_public_headers += \ src/libostree/ostree-remote.h \ src/libostree/ostree-repo-finder.h \ src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ $(NULL) endif diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 4b968abe..01aa8663 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -156,11 +156,13 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-remote.h \ src/libostree/ostree-repo-finder.h \ src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ $(NULL) else # if ENABLE_EXPERIMENTAL_API libostree_1_la_SOURCES += \ src/libostree/ostree-repo-finder.c \ src/libostree/ostree-repo-finder-config.c \ + src/libostree/ostree-repo-finder-mount.c \ $(NULL) endif @@ -237,7 +239,7 @@ OSTree_1_0_gir_INCLUDES = Gio-2.0 OSTree_1_0_gir_CFLAGS = $(libostree_1_la_CFLAGS) OSTree_1_0_gir_LIBS = libostree-1.la OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree -OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h %/ostree-repo-finder-config.h,$(libostree_1_la_SOURCES)) +OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h %/ostree-repo-finder.h %/ostree-repo-finder-config.h %/ostree-repo-finder-mount.h,$(libostree_1_la_SOURCES)) INTROSPECTION_GIRS += OSTree-1.0.gir gir_DATA += OSTree-1.0.gir typelib_DATA += OSTree-1.0.typelib diff --git a/Makefile-tests.am b/Makefile-tests.am index 1cdf8826..09c85818 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -196,6 +196,7 @@ _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-u if ENABLE_EXPERIMENTAL_API test_programs += \ tests/test-repo-finder-config \ + tests/test-repo-finder-mount \ $(NULL) endif @@ -210,7 +211,7 @@ common_tests_cflags = $(ostree_bin_shared_cflags) $(OT_INTERNAL_GIO_UNIX_CFLAGS) common_tests_ldadd = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_GIO_UNIX_LIBS) noinst_LTLIBRARIES += libostreetest.la -libostreetest_la_SOURCES = tests/libostreetest.c +libostreetest_la_SOURCES = tests/libostreetest.c tests/test-mock-gio.c tests/test-mock-gio.h libostreetest_la_CFLAGS = $(common_tests_cflags) -I $(srcdir)/tests libostreetest_la_LIBADD = $(common_tests_ldadd) @@ -229,6 +230,10 @@ tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS) tests_test_repo_finder_config_LDADD = $(TESTS_LDADD) +tests_test_repo_finder_mount_SOURCES = tests/test-repo-finder-mount.c +tests_test_repo_finder_mount_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_mount_LDADD = $(TESTS_LDADD) + tests_test_mutable_tree_CFLAGS = $(TESTS_CFLAGS) tests_test_mutable_tree_LDADD = $(TESTS_LDADD) diff --git a/Makefile.am b/Makefile.am index 53b505e3..0939d4b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,7 +29,7 @@ AM_CPPFLAGS += -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \ -DOSTREE_COMPILATION \ -DG_LOG_DOMAIN=\"OSTree\" \ -DOSTREE_GITREV='"$(OSTREE_GITREV)"' \ - -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_40 \ + -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_50 \ -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_40 -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_48 AM_CFLAGS += -std=gnu99 $(WARN_CFLAGS) AM_DISTCHECK_CONFIGURE_FLAGS += \ diff --git a/apidoc/ostree-experimental-sections.txt b/apidoc/ostree-experimental-sections.txt index 93210d56..c977b56c 100644 --- a/apidoc/ostree-experimental-sections.txt +++ b/apidoc/ostree-experimental-sections.txt @@ -58,6 +58,14 @@ ostree_repo_finder_config_get_type </SECTION> <SECTION> +<FILE>ostree-repo-finder-mount</FILE> +OstreeRepoFinderMount +ostree_repo_finder_mount_new +<SUBSECTION Standard> +ostree_repo_finder_mount_get_type +</SECTION> + +<SECTION> <FILE>ostree-misc-experimental</FILE> ostree_repo_get_collection_id ostree_repo_set_collection_id diff --git a/src/libostree/libostree-experimental.sym b/src/libostree/libostree-experimental.sym index 79fcdbe9..e70c80bb 100644 --- a/src/libostree/libostree-experimental.sym +++ b/src/libostree/libostree-experimental.sym @@ -50,6 +50,8 @@ global: ostree_repo_finder_config_get_type; ostree_repo_finder_config_new; ostree_repo_finder_get_type; + ostree_repo_finder_mount_get_type; + ostree_repo_finder_mount_new; ostree_repo_finder_resolve_async; ostree_repo_finder_resolve_all_async; ostree_repo_finder_resolve_all_finish; diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h index ac0aa1fb..dd3c9778 100644 --- a/src/libostree/ostree-autocleanups.h +++ b/src/libostree/ostree-autocleanups.h @@ -65,6 +65,7 @@ G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_fre G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRemote, ostree_remote_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, 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 (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 4c117110..a56fdc0b 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -190,6 +190,9 @@ G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_res #include "ostree-repo-finder-config.h" G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) + +#include "ostree-repo-finder-mount.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) #endif G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-mount.c b/src/libostree/ostree-repo-finder-mount.c new file mode 100644 index 00000000..ffe31e99 --- /dev/null +++ b/src/libostree/ostree-repo-finder-mount.c @@ -0,0 +1,547 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * 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 <gio/gio.h> +#include <glib.h> +#include <glib-object.h> +#include <libglnx.h> +#include <stdlib.h> + +#include "ostree-autocleanups.h" +#include "ostree-remote-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-mount.h" + +/** + * SECTION:ostree-repo-finder-mount + * @title: OstreeRepoFinderMount + * @short_description: Finds remote repositories from ref names by looking at + * mounted removable volumes + * @stability: Unstable + * @include: libostree/ostree-repo-finder-mount.h + * + * #OstreeRepoFinderMount is an implementation of #OstreeRepoFinder which looks + * refs up in well-known locations on any mounted removable volumes. + * + * For an #OstreeCollectionRef, (`C`, `R`), it checks whether `.ostree/repos/C/R` + * exists and is an OSTree repository on each mounted removable volume. Collection + * IDs and ref names are not escaped when building the path, so if either + * contains `/` in its name, the repository will be checked for in a + * subdirectory of `.ostree/repos`. Non-removable volumes are ignored. + * + * For each repository which is found, a result will be returned for the + * intersection of the refs being searched for, and the refs in `refs/heads` and + * `refs/mirrors` in the repository on the removable volume. + * + * Symlinks are followed when resolving the refs, so a volume might contain a + * single OSTree at some arbitrary path, with a number of refs linking to it + * from `.ostree/repos`. Any symlink which points outside the volume’s file + * system will be ignored. Repositories are deduplicated in the results. + * + * The volume monitor used to find mounted volumes can be overridden by setting + * #OstreeRepoFinderMount:monitor. By default, g_volume_monitor_get() is used. + * + * Since: 2017.8 + */ + +typedef GList/*<owned GObject>*/ ObjectList; + +static void +object_list_free (ObjectList *list) +{ + g_list_free_full (list, g_object_unref); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ObjectList, object_list_free) + +static void ostree_repo_finder_mount_iface_init (OstreeRepoFinderInterface *iface); + +struct _OstreeRepoFinderMount +{ + GObject parent_instance; + + GVolumeMonitor *monitor; /* owned */ +}; + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderMount, ostree_repo_finder_mount, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_mount_iface_init)) + +typedef struct +{ + gchar *uri; + gchar *keyring; +} UriAndKeyring; + +static void +uri_and_keyring_free (UriAndKeyring *data) +{ + g_free (data->uri); + g_free (data->keyring); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UriAndKeyring, uri_and_keyring_free) + +static UriAndKeyring * +uri_and_keyring_new (const gchar *uri, + const gchar *keyring) +{ + g_autoptr(UriAndKeyring) data = NULL; + + data = g_new0 (UriAndKeyring, 1); + data->uri = g_strdup (uri); + data->keyring = g_strdup (keyring); + + return g_steal_pointer (&data); +} + +static guint +uri_and_keyring_hash (gconstpointer key) +{ + const UriAndKeyring *_key = key; + + return g_str_hash (_key->uri) ^ g_str_hash (_key->keyring); +} + +static gboolean +uri_and_keyring_equal (gconstpointer a, + gconstpointer b) +{ + const UriAndKeyring *_a = a, *_b = b; + + return g_str_equal (_a->uri, _b->uri) && g_str_equal (_a->keyring, _b->keyring); +} + +/* This must return a valid remote name (suitable for use in a refspec). */ +static gchar * +uri_and_keyring_to_name (UriAndKeyring *data) +{ + g_autofree gchar *escaped_uri = g_uri_escape_string (data->uri, NULL, FALSE); + g_autofree gchar *escaped_keyring = g_uri_escape_string (data->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); +} + +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); +} + +static void +ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (finder); + g_autoptr(GTask) task = NULL; + g_autoptr(ObjectList) mounts = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + GList *l; + const gint priority = 50; /* arbitrarily chosen */ + + task = g_task_new (finder, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_mount_resolve_async); + + mounts = g_volume_monitor_get_mounts (self->monitor); + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + + g_debug ("%s: Found %u mounts", G_STRFUNC, g_list_length (mounts)); + + for (l = mounts; l != NULL; l = l->next) + { + GMount *mount = G_MOUNT (l->data); + g_autofree gchar *mount_name = NULL; + g_autoptr(GFile) mount_root = NULL; + g_autofree gchar *mount_root_path = NULL; + glnx_fd_close int mount_root_dfd = -1; + struct stat mount_root_stbuf; + glnx_fd_close int repos_dfd = -1; + gsize i; + g_autoptr(GHashTable) repo_to_refs = NULL; /* (element-type UriAndKeyring GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + UriAndKeyring *repo; + g_autoptr(GError) local_error = NULL; + + mount_name = g_mount_get_name (mount); + + /* Check the mount’s general properties. */ + if (g_mount_is_shadowed (mount)) + { + g_debug ("Ignoring mount ‘%s’ as it’s shadowed.", mount_name); + continue; + } + + /* Check if it contains a .ostree/repos directory. */ + mount_root = g_mount_get_root (mount); + mount_root_path = g_file_get_path (mount_root); + + if (!glnx_opendirat (AT_FDCWD, mount_root_path, TRUE, &mount_root_dfd, &local_error)) + { + g_debug ("Ignoring mount ‘%s’ as ‘%s’ directory can’t be opened: %s", + mount_name, mount_root_path, local_error->message); + continue; + } + + if (!glnx_opendirat (mount_root_dfd, ".ostree/repos", TRUE, &repos_dfd, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + g_debug ("Ignoring mount ‘%s’ as ‘%s/.ostree/repos’ directory doesn’t exist.", + mount_name, mount_root_path); + else + g_debug ("Ignoring mount ‘%s’ as ‘%s/.ostree/repos’ directory can’t be opened: %s", + mount_name, mount_root_path, local_error->message); + + continue; + } + + /* stat() the mount root so we can later check whether the resolved + * repositories for individual refs are on the same device (to avoid the + * symlinks for them pointing outside the mount root). */ + if (!glnx_fstat (mount_root_dfd, &mount_root_stbuf, &local_error)) + { + g_debug ("Ignoring mount ‘%s’ as querying info of ‘%s’ failed: %s", + mount_name, mount_root_path, local_error->message); + continue; + } + + /* Check whether a subdirectory exists for any of the @refs we’re looking + * for. If so, and it’s a symbolic link, dereference it so multiple links + * to the same repository (containing multiple refs) are coalesced. + * Otherwise, include it as a result by itself. */ + repo_to_refs = g_hash_table_new_full (uri_and_keyring_hash, uri_and_keyring_equal, + (GDestroyNotify) uri_and_keyring_free, (GDestroyNotify) g_hash_table_unref); + + for (i = 0; refs[i] != NULL; i++) + { + struct stat stbuf; + g_autofree gchar *collection_and_ref = NULL; + g_autofree gchar *repo_dir_path = NULL; + g_autofree gchar *resolved_repo_uri = NULL; + g_autofree gchar *keyring = NULL; + g_autoptr(UriAndKeyring) resolved_repo = NULL; + + collection_and_ref = g_build_filename (refs[i]->collection_id, refs[i]->ref_name, NULL); + repo_dir_path = g_build_filename (mount_root_path, ".ostree", "repos", + collection_and_ref, NULL); + + if (!glnx_fstatat (repos_dfd, collection_and_ref, &stbuf, AT_NO_AUTOMOUNT, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as querying info of ‘%s’ failed: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, repo_dir_path, local_error->message); + g_clear_error (&local_error); + continue; + } + + if ((stbuf.st_mode & S_IFMT) != S_IFDIR) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as ‘%s’ is of type %u, not a directory.", + refs[i]->collection_id, refs[i]->ref_name, mount_name, repo_dir_path, (stbuf.st_mode & S_IFMT)); + g_clear_error (&local_error); + continue; + } + + /* Check the resolved repository path is below the mount point. Do not + * allow ref symlinks to point somewhere outside of the mounted + * volume. */ + if (stbuf.st_dev != mount_root_stbuf.st_dev) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as it’s on a different file system from the mount.", + refs[i]->collection_id, refs[i]->ref_name, mount_name); + g_clear_error (&local_error); + continue; + } + + /* Exclude repositories which resolve to @parent_repo. */ + g_autofree char *canonical_repo_dir_path = realpath (repo_dir_path, NULL); + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (parent_repo)); + g_autofree char *canonical_parent_repo_path = realpath (parent_repo_path, NULL); + + if (g_strcmp0 (canonical_repo_dir_path, canonical_parent_repo_path) == 0) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository was the one we are resolving for: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, canonical_parent_repo_path); + g_clear_error (&local_error); + continue; + } + + /* Grab the given ref and a checksum for it from the repo. + * FIXME: Ideally, there would be some ostree_repo_open_at() which we + * could use to keep the openat() chain going. See + * https://github.com/ostreedev/ostree/pull/820. */ + g_autoptr(OstreeRepo) repo = NULL; + g_autoptr(GFile) repo_dir_file = g_file_new_for_path (repo_dir_path); + repo = ostree_repo_new (repo_dir_file); + + if (!ostree_repo_open (repo, cancellable, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository could not be opened: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + g_autoptr(GHashTable) repo_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + + if (!ostree_repo_list_collection_refs (repo, refs[i]->collection_id, &repo_refs, cancellable, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its refs could not be listed: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + const gchar *checksum = g_hash_table_lookup (repo_refs, refs[i]); + + if (checksum == NULL) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository doesn’t contain the ref.", + refs[i]->collection_id, refs[i]->ref_name, mount_name); + g_clear_error (&local_error); + continue; + } + + /* Finally, look up the GPG keyring for this ref. */ + keyring = ostree_repo_resolve_keyring_for_collection (parent_repo, refs[i]->collection_id, + cancellable, &local_error); + + if (keyring == NULL) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ due to missing keyring: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + /* There is a valid repo at (or pointed to by) + * $mount_root/.ostree/repos/$refs[i]->collection_id/$refs[i]->ref_name. + * Add it to the results, keyed by the canonicalised repository URI + * to deduplicate the results. */ + resolved_repo_uri = g_strconcat ("file://", canonical_repo_dir_path, NULL); + g_debug ("Resolved ref (%s, %s) on mount ‘%s’ to repo URI ‘%s’ with keyring ‘%s’.", + refs[i]->collection_id, refs[i]->ref_name, mount_name, resolved_repo_uri, keyring); + + resolved_repo = uri_and_keyring_new (resolved_repo_uri, keyring); + + supported_ref_to_checksum = g_hash_table_lookup (repo_to_refs, resolved_repo); + + 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_to_refs, g_steal_pointer (&resolved_repo), supported_ref_to_checksum /* transfer */); + } + + g_hash_table_insert (supported_ref_to_checksum, (gpointer) refs[i], g_strdup (checksum)); + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &repo, (gpointer *) &supported_ref_to_checksum)) + { + g_autoptr(OstreeRemote) remote = NULL; + + /* 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 (repo); + remote = ostree_remote_new (name); + + g_clear_pointer (&remote->keyring, g_free); + remote->keyring = g_strdup (repo->keyring); + + g_key_file_set_string (remote->options, remote->group, "url", repo->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", TRUE); + + /* Set the timestamp in the #OstreeRepoFinderResult to 0 because + * the code in ostree_repo_pull_from_remotes_async() will be able to + * check it just as quickly as we can here; so don’t duplicate the + * code. */ + 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_mount_resolve_finish (OstreeRepoFinder *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_mount_init (OstreeRepoFinderMount *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_mount_constructed (GObject *object) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + G_OBJECT_CLASS (ostree_repo_finder_mount_parent_class)->constructed (object); + + if (self->monitor == NULL) + self->monitor = g_volume_monitor_get (); +} + +typedef enum +{ + PROP_MONITOR = 1, +} OstreeRepoFinderMountProperty; + +static void +ostree_repo_finder_mount_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + switch ((OstreeRepoFinderMountProperty) property_id) + { + case PROP_MONITOR: + g_value_set_object (value, self->monitor); + break; + default: + g_assert_not_reached (); + } +} + +static void +ostree_repo_finder_mount_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + switch ((OstreeRepoFinderMountProperty) property_id) + { + case PROP_MONITOR: + /* Construct-only. */ + g_assert (self->monitor == NULL); + self->monitor = g_value_dup_object (value); + break; + default: + g_assert_not_reached (); + } +} + +static void +ostree_repo_finder_mount_dispose (GObject *object) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + g_clear_object (&self->monitor); + + G_OBJECT_CLASS (ostree_repo_finder_mount_parent_class)->dispose (object); +} + +static void +ostree_repo_finder_mount_class_init (OstreeRepoFinderMountClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ostree_repo_finder_mount_get_property; + object_class->set_property = ostree_repo_finder_mount_set_property; + object_class->constructed = ostree_repo_finder_mount_constructed; + object_class->dispose = ostree_repo_finder_mount_dispose; + + /** + * OstreeRepoFinderMount:monitor: + * + * Volume monitor to use to look up mounted volumes when queried. + * + * Since: 2017.8 + */ + g_object_class_install_property (object_class, PROP_MONITOR, + g_param_spec_object ("monitor", + "Volume Monitor", + "Volume monitor to use " + "to look up mounted " + "volumes when queried.", + G_TYPE_VOLUME_MONITOR, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +ostree_repo_finder_mount_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_mount_resolve_async; + iface->resolve_finish = ostree_repo_finder_mount_resolve_finish; +} + +/** + * ostree_repo_finder_mount_new: + * @monitor: (nullable) (transfer none): volume monitor to use, or %NULL to use + * the system default + * + * Create a new #OstreeRepoFinderMount, using the given @monitor to look up + * volumes. If @monitor is %NULL, the monitor from g_volume_monitor_get() will + * be used. + * + * Returns: (transfer full): a new #OstreeRepoFinderMount + * Since: 2017.8 + */ +OstreeRepoFinderMount * +ostree_repo_finder_mount_new (GVolumeMonitor *monitor) +{ + g_return_val_if_fail (monitor == NULL || G_IS_VOLUME_MONITOR (monitor), NULL); + + return g_object_new (OSTREE_TYPE_REPO_FINDER_MOUNT, + "monitor", monitor, + NULL); +} diff --git a/src/libostree/ostree-repo-finder-mount.h b/src/libostree/ostree-repo-finder-mount.h new file mode 100644 index 00000000..d844dd44 --- /dev/null +++ b/src/libostree/ostree-repo-finder-mount.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * 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_MOUNT (ostree_repo_finder_mount_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderMount, ostree_repo_finder_mount, OSTREE, REPO_FINDER_MOUNT, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_mount_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderMount OstreeRepoFinderMount; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderMountClass; + +static inline OstreeRepoFinderMount *OSTREE_REPO_FINDER_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_mount_get_type (), OstreeRepoFinderMount); } +static inline gboolean OSTREE_IS_REPO_FINDER_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_mount_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderMount *ostree_repo_finder_mount_new (GVolumeMonitor *monitor); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 56e4839e..ecc9b72c 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -42,6 +42,7 @@ #ifdef OSTREE_ENABLE_EXPERIMENTAL_API #include "ostree-repo-finder.h" #include "ostree-repo-finder-config.h" +#include "ostree-repo-finder-mount.h" #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #include <gio/gunixinputstream.h> @@ -4087,6 +4088,7 @@ ostree_repo_find_remotes_async (OstreeRepo *self, GMainContext *context; OstreeRepoFinder *default_finders[4] = { NULL, }; g_autoptr(OstreeRepoFinder) finder_config = NULL; + g_autoptr(OstreeRepoFinder) finder_mount = NULL; g_return_if_fail (OSTREE_IS_REPO (self)); g_return_if_fail (is_valid_collection_ref_array (refs)); @@ -4106,8 +4108,10 @@ ostree_repo_find_remotes_async (OstreeRepo *self, if (finders == NULL) { finder_config = OSTREE_REPO_FINDER (ostree_repo_finder_config_new ()); + finder_mount = OSTREE_REPO_FINDER (ostree_repo_finder_mount_new (NULL)); default_finders[0] = finder_config; + default_finders[1] = finder_mount; finders = default_finders; } diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h index 8d547041..0fe2a23e 100644 --- a/src/libostree/ostree.h +++ b/src/libostree/ostree.h @@ -39,6 +39,7 @@ #include <ostree-ref.h> #include <ostree-repo-finder.h> #include <ostree-repo-finder-config.h> +#include <ostree-repo-finder-mount.h> #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #include <ostree-autocleanups.h> diff --git a/tests/.gitignore b/tests/.gitignore index bf31dd01..f3bdb177 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -15,4 +15,5 @@ test-ot-opt-utils test-ot-tool-util test-ot-unix-utils test-repo-finder-config +test-repo-finder-mount test-rollsum-cli diff --git a/tests/test-mock-gio.c b/tests/test-mock-gio.c new file mode 100644 index 00000000..f6d4f2a8 --- /dev/null +++ b/tests/test-mock-gio.c @@ -0,0 +1,400 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * 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 <gio/gio.h> +#include <glib.h> +#include <glib-object.h> +#include <libglnx.h> + +#include "test-mock-gio.h" + +/** + * SECTION:mock-gio + * @title: Mock GIO volume interfaces + * @short_description: Mock implementations of GIO volume, mount and drive + * interfaces + * @stability: Unstable + * @include: tests/test-mock-gio.h + * + * A set of classes implementing GIO interfaces for volumes, mounts, drives + * and volume monitoring, which return mock data to the caller when used. These + * are designed for use in unit tests, to mock up removable drives when testing + * code which monitors such drives being added and removed and then queries + * properties of them. + * + * By returning mock drive locations to the caller, for example, the contents of + * a removable drive may be mocked up using temporary files. + * + * Currently, all the mock data returned by these classes to callers is static, + * set at construction time. + * + * Since: 2017.8 + */ + +/* Mock volume monitor class. This returns a static set of data to the caller, + * which it was initialised with. */ +struct _OstreeMockVolumeMonitor +{ + GVolumeMonitor parent_instance; + + GList *mounts; /* (element-type OstreeMockMount) */ + GList *volumes; /* (element-type OstreeMockVolume) */ +}; + +G_DEFINE_TYPE (OstreeMockVolumeMonitor, ostree_mock_volume_monitor, G_TYPE_VOLUME_MONITOR) + +static GList * +ostree_mock_volume_monitor_get_mounts (GVolumeMonitor *monitor) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor); + return g_list_copy_deep (self->mounts, (GCopyFunc) g_object_ref, NULL); +} + +static GList * +ostree_mock_volume_monitor_get_volumes (GVolumeMonitor *monitor) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor); + return g_list_copy_deep (self->volumes, (GCopyFunc) g_object_ref, NULL); +} + +static void +ostree_mock_volume_monitor_init (OstreeMockVolumeMonitor *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_volume_monitor_dispose (GObject *object) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (object); + + g_list_free_full (self->volumes, g_object_unref); + self->volumes = NULL; + + g_list_free_full (self->mounts, g_object_unref); + self->mounts = NULL; + + G_OBJECT_CLASS (ostree_mock_volume_monitor_parent_class)->dispose (object); +} + +static void +ostree_mock_volume_monitor_class_init (OstreeMockVolumeMonitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + + object_class->dispose = ostree_mock_volume_monitor_dispose; + + monitor_class->get_mounts = ostree_mock_volume_monitor_get_mounts; + monitor_class->get_volumes = ostree_mock_volume_monitor_get_volumes; +} + +/** + * ostree_mock_volume_monitor_new: + * @mounts: (element-type GMount) (transfer none): list of current #GMounts + * @volumes: (element-type GVolume) (transfer none): list of current #GVolumes + * + * Create a new mock #GVolumeMonitor which will return the given static lists of + * #GMounts and #GVolumes to any caller of g_volume_monitor_get_mounts() or + * g_volume_monitor_get_volumes(). + * + * Typically, the elements of @mounts will be #OstreeMockMount objects and the + * elements of @volumes will be #OstreeMockVolume objects; but this does not + * have to be the case. + * + * Returns: (transfer full): a new #GVolumeMonitor object + * Since: 2017.8 + */ +GVolumeMonitor * +ostree_mock_volume_monitor_new (GList *mounts, + GList *volumes) +{ + g_autoptr(OstreeMockVolumeMonitor) monitor = NULL; + + monitor = g_object_new (OSTREE_TYPE_MOCK_VOLUME_MONITOR, NULL); + monitor->mounts = g_list_copy_deep (mounts, (GCopyFunc) g_object_ref, NULL); + monitor->volumes = g_list_copy_deep (volumes, (GCopyFunc) g_object_ref, NULL); + + return g_steal_pointer (&monitor); +} + +/* Mock volume class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockVolume +{ + GObject parent_instance; + + gchar *name; + GDrive *drive; /* (owned) (nullable) */ + GMount *mount; /* (owned) (nullable) */ +}; + +static void ostree_mock_volume_iface_init (GVolumeIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockVolume, ostree_mock_volume, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, ostree_mock_volume_iface_init)) + +static gchar * +ostree_mock_volume_get_name (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return g_strdup (self->name); +} + +static GDrive * +ostree_mock_volume_get_drive (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return (self->drive != NULL) ? g_object_ref (self->drive) : NULL; +} + +static GMount * +ostree_mock_volume_get_mount (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return (self->mount != NULL) ? g_object_ref (self->mount) : NULL; +} + +static void +ostree_mock_volume_init (OstreeMockVolume *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_volume_dispose (GObject *object) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (object); + + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->drive); + g_clear_object (&self->mount); + + G_OBJECT_CLASS (ostree_mock_volume_parent_class)->dispose (object); +} + +static void +ostree_mock_volume_class_init (OstreeMockVolumeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ostree_mock_volume_dispose; +} + +static void +ostree_mock_volume_iface_init (GVolumeIface *iface) +{ + iface->get_name = ostree_mock_volume_get_name; + iface->get_drive = ostree_mock_volume_get_drive; + iface->get_mount = ostree_mock_volume_get_mount; +} + +/** + * ostree_mock_volume_new: + * @name: volume name + * @drive: (transfer none) (nullable): drive for the volume, or %NULL if none + * should be associated + * @mount: (transfer none) (nullable): mount for the volume, or %NULL if it’s + * not mounted + * + * Create a new mock #GVolume which will return the given static @name, @drive + * and @mount to any caller of its getter methods. There is currently no + * provision for changing these values dynamically. There is also currently no + * provision for mocking the other getters of #GVolume. + * + * Typically, @drive will be an #OstreeMockDrive object and @mount will be an + * #OstreeMockMount object; but this does not have to be the case. + * + * Returns: (transfer full): a new #GVolume object + * Since: 2017.8 + */ +OstreeMockVolume * +ostree_mock_volume_new (const gchar *name, + GDrive *drive, + GMount *mount) +{ + g_autoptr(OstreeMockVolume) volume = NULL; + + volume = g_object_new (OSTREE_TYPE_MOCK_VOLUME, NULL); + volume->name = g_strdup (name); + volume->drive = (drive != NULL) ? g_object_ref (drive) : NULL; + volume->mount = (mount != NULL) ? g_object_ref (mount) : NULL; + + return g_steal_pointer (&volume); +} + +/* Mock drive class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockDrive +{ + GObject parent_instance; + + gboolean is_removable; +}; + +static void ostree_mock_drive_iface_init (GDriveIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockDrive, ostree_mock_drive, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_DRIVE, ostree_mock_drive_iface_init)) + +#if GLIB_CHECK_VERSION(2, 50, 0) +static gboolean +ostree_mock_drive_is_removable (GDrive *drive) +{ + OstreeMockDrive *self = OSTREE_MOCK_DRIVE (drive); + return self->is_removable; +} +#endif + +static void +ostree_mock_drive_init (OstreeMockDrive *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_drive_class_init (OstreeMockDriveClass *klass) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_drive_iface_init (GDriveIface *iface) +{ +#if GLIB_CHECK_VERSION(2, 50, 0) + iface->is_removable = ostree_mock_drive_is_removable; +#endif +} + +/** + * ostree_mock_drive_new: + * @is_removable: %TRUE if the drive is removable; %FALSE otherwise + * + * Create a new mock #GDrive which will return the given static @is_removable to + * any caller of its getter methods. There is currently no provision for mocking + * the other getters of #GDrive. + * + * Returns: (transfer full): a new #GDrive object + * Since: 2017.8 + */ +OstreeMockDrive * +ostree_mock_drive_new (gboolean is_removable) +{ + g_autoptr(OstreeMockDrive) drive = NULL; + + drive = g_object_new (OSTREE_TYPE_MOCK_DRIVE, NULL); + drive->is_removable = is_removable; + + return g_steal_pointer (&drive); +} + +/* Mock mount class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockMount +{ + GObject parent_instance; + + gchar *name; /* (owned) */ + GFile *root; /* (owned) */ +}; + +static void ostree_mock_mount_iface_init (GMountIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockMount, ostree_mock_mount, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_MOUNT, ostree_mock_mount_iface_init)) + +static gchar * +ostree_mock_mount_get_name (GMount *mount) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount); + return g_strdup (self->name); +} + +static GFile * +ostree_mock_mount_get_root (GMount *mount) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount); + return g_object_ref (self->root); +} + +static void +ostree_mock_mount_init (OstreeMockMount *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_mount_dispose (GObject *object) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (object); + + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->root); + + G_OBJECT_CLASS (ostree_mock_mount_parent_class)->dispose (object); +} + +static void +ostree_mock_mount_class_init (OstreeMockMountClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ostree_mock_mount_dispose; +} + +static void +ostree_mock_mount_iface_init (GMountIface *iface) +{ + iface->get_name = ostree_mock_mount_get_name; + iface->get_root = ostree_mock_mount_get_root; +} + +/** + * ostree_mock_mount_new: + * @name: mount name + * @root: (transfer none): root path for the mounted file system + * + * Create a new mock #GMount which will return the given static @name and @root + * to any caller of its getter methods. There is currently no provision for + * mocking the other getters of #GMount. + * + * Typically, @root will point to a temporary directory where a mocked file + * system is present; but this does not have to be the case. + * + * Returns: (transfer full): a new #GMount object + * Since: 2017.8 + */ +OstreeMockMount * +ostree_mock_mount_new (const gchar *name, + GFile *root) +{ + g_autoptr(OstreeMockMount) mount = NULL; + + mount = g_object_new (OSTREE_TYPE_MOCK_MOUNT, NULL); + mount->name = g_strdup (name); + mount->root = g_object_ref (root); + + return g_steal_pointer (&mount); +} diff --git a/tests/test-mock-gio.h b/tests/test-mock-gio.h new file mode 100644 index 00000000..9f544759 --- /dev/null +++ b/tests/test-mock-gio.h @@ -0,0 +1,127 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * 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 <libglnx.h> + +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_MOCK_VOLUME_MONITOR (ostree_mock_volume_monitor_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockVolumeMonitor, ostree_mock_volume_monitor, OSTREE, MOCK_VOLUME_MONITOR, GVolumeMonitor) */ + +G_GNUC_INTERNAL +GType ostree_mock_volume_monitor_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockVolumeMonitor OstreeMockVolumeMonitor; +typedef struct { GVolumeMonitorClass parent_class; } OstreeMockVolumeMonitorClass; + +static inline OstreeMockVolumeMonitor *OSTREE_MOCK_VOLUME_MONITOR (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_volume_monitor_get_type (), OstreeMockVolumeMonitor); } +static inline gboolean OSTREE_IS_MOCK_VOLUME_MONITOR (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_volume_monitor_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockVolumeMonitor, g_object_unref) + +G_GNUC_INTERNAL +GVolumeMonitor *ostree_mock_volume_monitor_new (GList *mounts, + GList *volumes); + +#define OSTREE_TYPE_MOCK_VOLUME (ostree_mock_volume_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockVolume, ostree_mock_volume, OSTREE, MOCK_VOLUME, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_volume_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockVolume OstreeMockVolume; +typedef struct { GObjectClass parent_class; } OstreeMockVolumeClass; + +static inline OstreeMockVolume *OSTREE_MOCK_VOLUME (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_volume_get_type (), OstreeMockVolume); } +static inline gboolean OSTREE_IS_MOCK_VOLUME (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_volume_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockVolume, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockVolume *ostree_mock_volume_new (const gchar *name, + GDrive *drive, + GMount *mount); + +#define OSTREE_TYPE_MOCK_DRIVE (ostree_mock_drive_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockDrive, ostree_mock_drive, OSTREE, MOCK_DRIVE, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_drive_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockDrive OstreeMockDrive; +typedef struct { GObjectClass parent_class; } OstreeMockDriveClass; + +static inline OstreeMockDrive *OSTREE_MOCK_DRIVE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_drive_get_type (), OstreeMockDrive); } +static inline gboolean OSTREE_IS_MOCK_DRIVE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_drive_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockDrive, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockDrive *ostree_mock_drive_new (gboolean is_removable); + +#define OSTREE_TYPE_MOCK_MOUNT (ostree_mock_mount_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockMount, ostree_mock_mount, OSTREE, MOCK_MOUNT, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_mount_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockMount OstreeMockMount; +typedef struct { GObjectClass parent_class; } OstreeMockMountClass; + +static inline OstreeMockMount *OSTREE_MOCK_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_mount_get_type (), OstreeMockMount); } +static inline gboolean OSTREE_IS_MOCK_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_mount_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockMount, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockMount *ostree_mock_mount_new (const gchar *name, + GFile *root); + +G_END_DECLS diff --git a/tests/test-repo-finder-mount.c b/tests/test-repo-finder-mount.c new file mode 100644 index 00000000..c84feb52 --- /dev/null +++ b/tests/test-repo-finder-mount.c @@ -0,0 +1,497 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * 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 <gio/gio.h> +#include <glib.h> +#include <glib-object.h> +#include <locale.h> + +#include "libostreetest.h" +#include "ostree-autocleanups.h" +#include "ostree-remote-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-mount.h" +#include "ostree-types.h" +#include "test-mock-gio.h" + +/* Test fixture. Creates a temporary directory and repository. */ +typedef struct +{ + OstreeRepo *parent_repo; + int working_dfd; /* owned */ + GFile *working_dir; /* owned */ +} Fixture; + +static void +setup (Fixture *fixture, + gconstpointer test_data) +{ + g_autofree gchar *tmp_name = NULL; + g_autoptr(GError) error = NULL; + + tmp_name = g_strdup ("test-repo-finder-mount-XXXXXX"); + glnx_mkdtempat_open_in_system (tmp_name, 0700, &fixture->working_dfd, &error); + g_assert_no_error (error); + + g_test_message ("Using temporary directory: %s", tmp_name); + + glnx_shutil_mkdir_p_at (fixture->working_dfd, "repo", 0700, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + g_autoptr(GFile) tmp_dir = g_file_new_for_path (g_get_tmp_dir ()); + fixture->working_dir = g_file_get_child (tmp_dir, tmp_name); + + fixture->parent_repo = ot_test_setup_repo (NULL, &error); + g_assert_no_error (error); +} + +static void +teardown (Fixture *fixture, + gconstpointer test_data) +{ + glnx_fd_close int parent_repo_dfd = -1; + g_autoptr(GError) error = NULL; + + /* Recursively remove the temporary directory. */ + glnx_shutil_rm_rf_at (fixture->working_dfd, ".", NULL, NULL); + + close (fixture->working_dfd); + fixture->working_dfd = -1; + + /* The repo also needs its source files to be removed. This is the inverse + * of setup_test_repository() in libtest.sh. */ + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (fixture->parent_repo)); + glnx_opendirat (-1, parent_repo_path, TRUE, &parent_repo_dfd, &error); + g_assert_no_error (error); + + glnx_shutil_rm_rf_at (parent_repo_dfd, "../files", NULL, NULL); + glnx_shutil_rm_rf_at (parent_repo_dfd, "../repo", NULL, NULL); + + g_clear_object (&fixture->working_dir); + g_clear_object (&fixture->parent_repo); +} + +/* Test the object constructor works at a basic level. */ +static void +test_repo_finder_mount_init (void) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + + /* Default #GVolumeMonitor. */ + finder = ostree_repo_finder_mount_new (NULL); + g_clear_object (&finder); + + /* Explicit #GVolumeMonitor. */ + monitor = ostree_mock_volume_monitor_new (NULL, NULL); + finder = ostree_repo_finder_mount_new (monitor); + g_clear_object (&finder); +} + +static void +result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + *result_out = g_object_ref (result); +} + +/* Test that no remotes are found if the #GVolumeMonitor returns no mounts. */ +static void +test_repo_finder_mount_no_mounts (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + const OstreeCollectionRef ref1 = { "org.example.Collection1", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/buildmaster/standard" }; + const OstreeCollectionRef ref3 = { "org.example.Collection2", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref4 = { "org.example.Collection2", "exampleos/arm64/standard" }; + const OstreeCollectionRef * const refs[] = { &ref1, &ref2, &ref3, &ref4, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + monitor = ostree_mock_volume_monitor_new (NULL, NULL); + finder = ostree_repo_finder_mount_new (monitor); + + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, + NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 0); + + g_main_context_pop_thread_default (context); +} + +/* Create a .ostree/repos directory under the given @mount_root, or abort. */ +static gboolean +assert_create_repos_dir (Fixture *fixture, + const gchar *mount_root_name, + int *out_repos_dfd, + GMount **out_mount) +{ + glnx_fd_close int repos_dfd = -1; + g_autoptr(GError) error = NULL; + + g_autofree gchar *path = g_build_filename (mount_root_name, ".ostree", "repos", NULL); + glnx_shutil_mkdir_p_at_open (fixture->working_dfd, path, 0700, &repos_dfd, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + *out_repos_dfd = glnx_steal_fd (&repos_dfd); + g_autoptr(GFile) mount_root = g_file_get_child (fixture->working_dir, mount_root_name); + *out_mount = G_MOUNT (ostree_mock_mount_new (mount_root_name, mount_root)); + + return TRUE; +} + +/* Create a new repository in @repo_dir with its collection ID unset, and + * containing the refs given in @... (which must be %NULL-terminated). Each + * #OstreeCollectionRef in @... is followed by a gchar** return address for the + * checksum committed for that ref. Return the new repository. */ +static OstreeRepo * +assert_create_remote_va (Fixture *fixture, + GFile *repo_dir, + va_list args) +{ + g_autoptr(GError) error = NULL; + + g_autoptr(OstreeRepo) repo = ostree_repo_new (repo_dir); + ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, NULL, &error); + g_assert_no_error (error); + + /* Set up the refs from @.... */ + for (const OstreeCollectionRef *ref = va_arg (args, const OstreeCollectionRef *); + ref != NULL; + ref = va_arg (args, const OstreeCollectionRef *)) + { + g_autofree gchar *checksum = NULL; + g_autoptr(OstreeMutableTree) mtree = NULL; + g_autoptr(OstreeRepoFile) repo_file = NULL; + gchar **out_checksum = va_arg (args, gchar **); + + mtree = ostree_mutable_tree_new (); + ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + g_assert_no_error (error); + ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); + g_assert_no_error (error); + + ostree_repo_write_commit (repo, NULL /* no parent */, ref->ref_name, ref->ref_name, + NULL /* no metadata */, repo_file, &checksum, + NULL, &error); + g_assert_no_error (error); + + if (ref->collection_id != NULL) + ostree_repo_set_collection_ref_immediate (repo, ref, checksum, NULL, &error); + else + ostree_repo_set_ref_immediate (repo, NULL, ref->ref_name, checksum, NULL, &error); + g_assert_no_error (error); + + if (out_checksum != NULL) + *out_checksum = g_steal_pointer (&checksum); + } + + /* Update the summary. */ + ostree_repo_regenerate_summary (repo, NULL /* no metadata */, NULL, &error); + g_assert_no_error (error); + + return g_steal_pointer (&repo); +} + +static OstreeRepo * +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const OstreeCollectionRef *ref, + gchar **out_uri, + ...) G_GNUC_NULL_TERMINATED; + +/* Create a @ref directory under the given @repos_dfd, or abort. Create a new + * repository in it with the refs given in @..., as per assert_create_remote_va(). + * Return the URI of the repository. */ +static OstreeRepo * +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const OstreeCollectionRef *ref, + gchar **out_uri, + ...) +{ + glnx_fd_close int ref_dfd = -1; + g_autoptr(OstreeRepo) repo = NULL; + g_autoptr(GError) error = NULL; + va_list args; + + g_autofree gchar *path = g_build_filename (ref->collection_id, ref->ref_name, NULL); + glnx_shutil_mkdir_p_at_open (repos_dfd, path, 0700, &ref_dfd, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + g_autoptr(GFile) mount_root = g_mount_get_root (repos_mount); + g_autoptr(GFile) repos_dir = g_file_get_child (mount_root, ".ostree/repos"); + g_autoptr(GFile) repo_dir = g_file_get_child (repos_dir, path); + + va_start (args, out_uri); + repo = assert_create_remote_va (fixture, repo_dir, args); + va_end (args); + + *out_uri = g_file_get_uri (repo_dir); + + return g_steal_pointer (&repo); +} + +/* Create a @ref symlink under the given @repos_dfd, pointing to + * @symlink_target, or abort. */ +static int +assert_create_repo_symlink (int repos_dfd, + const OstreeCollectionRef *ref, + const gchar *symlink_target_path) +{ + glnx_fd_close int symlink_target_dfd = -1; + g_autoptr(GError) error = NULL; + + /* The @ref_parent_dir is not necessarily @collection_dir, since @ref may + * contain slashes. */ + g_autofree gchar *path = g_build_filename (ref->collection_id, ref->ref_name, NULL); + g_autofree gchar *path_parent = g_path_get_dirname (path); + + glnx_shutil_mkdir_p_at (repos_dfd, path_parent, 0700, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + if (TEMP_FAILURE_RETRY (symlinkat (symlink_target_path, repos_dfd, path)) != 0) + { + g_autoptr(GError) error = NULL; + glnx_throw_errno_prefix (&error, "symlinkat"); + g_assert_no_error (error); + } + + /* Return a dir FD for the symlink target. */ + glnx_opendirat (repos_dfd, path, TRUE, &symlink_target_dfd, &error); + g_assert_no_error (error); + + return glnx_steal_fd (&symlink_target_dfd); +} + +/* Add configuration for a remote named @remote_name, at @remote_uri, with a + * remote collection ID of @collection_id, to the given @repo. */ +static void +assert_create_remote_config (OstreeRepo *repo, + const gchar *remote_name, + const gchar *remote_uri, + const gchar *collection_id) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) options = NULL; + + if (collection_id != NULL) + options = g_variant_new_parsed ("@a{sv} { 'collection-id': <%s> }", + collection_id); + + ostree_repo_remote_add (repo, remote_name, remote_uri, options, NULL, &error); + g_assert_no_error (error); +} + +/* Test resolving the refs against a collection of mock volumes, some of which + * are mounted, some of which are removable, some of which contain valid or + * invalid repo information on the file system, etc. */ +static void +test_repo_finder_mount_mixed_mounts (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + g_autoptr(GList) mounts = NULL; /* (element-type OstreeMockMount) */ + g_autoptr(GMount) non_removable_mount = NULL; + g_autoptr(GMount) no_repos_mount = NULL; + g_autoptr(GMount) repo1_mount = NULL; + g_autoptr(GMount) repo2_mount = NULL; + g_autoptr(GFile) non_removable_root = NULL; + glnx_fd_close int no_repos_repos = -1; + glnx_fd_close int repo1_repos = -1; + glnx_fd_close int repo2_repos = -1; + g_autoptr(OstreeRepo) repo1_repo_a = NULL, repo1_repo_b = NULL; + g_autoptr(OstreeRepo) repo2_repo_a = NULL; + g_autofree gchar *repo1_repo_a_uri = NULL, *repo1_repo_b_uri = NULL; + g_autofree gchar *repo2_repo_a_uri = NULL; + g_autofree gchar *repo1_ref0_checksum = NULL, *repo1_ref1_checksum = NULL, *repo1_ref2_checksum = NULL; + g_autofree gchar *repo2_ref0_checksum = NULL, *repo2_ref1_checksum = NULL, *repo2_ref2_checksum = NULL; + g_autofree gchar *repo1_ref5_checksum = NULL; + gsize i; + const OstreeCollectionRef ref0 = { "org.example.Collection1", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef ref1 = { "org.example.Collection1", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/ref2" }; + const OstreeCollectionRef ref3 = { "org.example.Collection1", "exampleos/x86_64/ref3" }; + const OstreeCollectionRef ref4 = { "org.example.UnconfiguredCollection", "exampleos/x86_64/ref4" }; + const OstreeCollectionRef ref5 = { "org.example.Collection3", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef * const refs[] = { &ref0, &ref1, &ref2, &ref3, &ref4, &ref5, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + /* Build the various mock drives/volumes/mounts, and some repositories with + * refs within them. We use "/" under the assumption that it’s on a separate + * file system from /tmp, so it’s an example of a symlink pointing outside + * its mount point. */ + non_removable_root = g_file_get_child (fixture->working_dir, "non-removable-mount"); + non_removable_mount = G_MOUNT (ostree_mock_mount_new ("non-removable", non_removable_root)); + + assert_create_repos_dir (fixture, "no-repos-mount", &no_repos_repos, &no_repos_mount); + + assert_create_repos_dir (fixture, "repo1-mount", &repo1_repos, &repo1_mount); + repo1_repo_a = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, refs[0], &repo1_repo_a_uri, + refs[0], &repo1_ref0_checksum, + refs[2], &repo1_ref2_checksum, + refs[5], &repo1_ref5_checksum, + NULL); + repo1_repo_b = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, refs[1], &repo1_repo_b_uri, + refs[1], &repo1_ref1_checksum, + NULL); + assert_create_repo_symlink (repo1_repos, refs[2], "ref0"); /* repo1_repo_a */ + assert_create_repo_symlink (repo1_repos, refs[5], "../../../org.example.Collection1/exampleos/x86_64/ref0"); /* repo1_repo_a */ + + assert_create_repos_dir (fixture, "repo2-mount", &repo2_repos, &repo2_mount); + repo2_repo_a = assert_create_repo_dir (fixture, repo2_repos, repo2_mount, refs[0], &repo2_repo_a_uri, + refs[0], &repo2_ref0_checksum, + refs[1], &repo2_ref1_checksum, + refs[2], &repo2_ref2_checksum, + refs[3], NULL, + NULL); + assert_create_repo_symlink (repo2_repos, refs[1], "ref0"); /* repo2_repo_a */ + assert_create_repo_symlink (repo2_repos, refs[2], "ref1"); /* repo2_repo_b */ + assert_create_repo_symlink (repo2_repos, refs[3], "/"); + + mounts = g_list_prepend (mounts, non_removable_mount); + mounts = g_list_prepend (mounts, no_repos_mount); + mounts = g_list_prepend (mounts, repo1_mount); + mounts = g_list_prepend (mounts, repo2_mount); + + monitor = ostree_mock_volume_monitor_new (mounts, NULL); + finder = ostree_repo_finder_mount_new (monitor); + + assert_create_remote_config (fixture->parent_repo, "remote1", "https://nope1", "org.example.Collection1"); + assert_create_remote_config (fixture->parent_repo, "remote2", "https://nope2", "org.example.Collection2"); + /* don’t configure org.example.UnconfiguredCollection */ + assert_create_remote_config (fixture->parent_repo, "remote3", "https://nope3", "org.example.Collection3"); + + /* Resolve the refs. */ + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, + NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 4); + + /* Check that the results are correct: the invalid refs should have been + * ignored, and the valid results canonicalised and deduplicated. */ + for (i = 0; i < results->len; i++) + { + g_autofree gchar *uri = NULL; + const gchar *keyring; + const OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + + uri = g_key_file_get_string (result->remote->options, result->remote->group, "url", &error); + g_assert_no_error (error); + keyring = result->remote->keyring; + + if (g_strcmp0 (uri, repo1_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 2); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[0]), ==, repo1_ref0_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[2]), ==, repo1_ref2_checksum); + } + else if (g_strcmp0 (uri, repo1_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote3.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[5]), ==, repo1_ref5_checksum); + } + else if (g_strcmp0 (uri, repo1_repo_b_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[1]), ==, repo1_ref1_checksum); + } + else if (g_strcmp0 (uri, repo2_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 3); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[0]), ==, repo2_ref0_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[1]), ==, repo2_ref1_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[2]), ==, repo2_ref2_checksum); + } + else + { + g_test_message ("Unknown result ‘%s’ with keyring ‘%s’.", + result->remote->name, result->remote->keyring); + g_assert_not_reached (); + } + } + + g_main_context_pop_thread_default (context); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/repo-finder-mount/init", test_repo_finder_mount_init); + g_test_add ("/repo-finder-mount/no-mounts", Fixture, NULL, setup, + test_repo_finder_mount_no_mounts, teardown); + g_test_add ("/repo-finder-mount/mixed-mounts", Fixture, NULL, setup, + test_repo_finder_mount_mixed_mounts, teardown); + + return g_test_run(); +} |