From 15247641d9e4bf6190954da806c8c08136d06f29 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 15 Sep 2017 15:59:32 +0100 Subject: [PATCH] lib/repo-finder-mount: Change the schema for finding repos on volumes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See issue #1174 for the rationale behind this. In summary: • It required two lists of collection–refs to be maintained: one in the repository, and one pointing to the repository. • It didn’t automatically work for live USBs of OSs based on OSTree (where there’s always a repository at /ostree/repo). • It was unnecessarily complex. The new scheme allows a list of repositories to be searched, but without needing a layer of indirection through their collection–refs. It adds /ostree/repo and /.ostree/repo as well-known repository locations which are always checked on a mounted volume (if they exist). Update the unit tests accordingly. Signed-off-by: Philip Withnall https://github.com/ostreedev/ostree/issues/1174 Closes: #1179 Approved by: cgwalters --- src/libostree/ostree-repo-finder-mount.c | 384 +++++++++++++++-------- tests/test-repo-finder-mount.c | 193 ++++++++---- 2 files changed, 388 insertions(+), 189 deletions(-) diff --git a/src/libostree/ostree-repo-finder-mount.c b/src/libostree/ostree-repo-finder-mount.c index b7b15bf0..31bb8bfc 100644 --- a/src/libostree/ostree-repo-finder-mount.c +++ b/src/libostree/ostree-repo-finder-mount.c @@ -46,19 +46,20 @@ * #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 mounted removable volume, the directory `.ostree/repos.d` will be + * enumerated, and all OSTree repositories below it will be searched, in lexical + * order, for the requested #OstreeCollectionRefs. The names of the directories + * below `.ostree/repos.d` are irrelevant, apart from their lexical ordering. + * The directory `ostree/repo` will be searched after the others, if it exists. + * 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 + * Symlinks are followed when listing the repositories, so a volume might + * contain a single OSTree at some arbitrary path, with a symlink from + * `.ostree/repos.d`. 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 @@ -166,6 +167,137 @@ results_compare_cb (gconstpointer a, return ostree_repo_finder_result_compare (result_a, result_b); } +typedef struct +{ + char *ordering_name; /* (owned) */ + OstreeRepo *repo; /* (owned) */ + GHashTable *refs; /* (owned) (element-type OstreeCollectionRef utf8) */ +} RepoAndRefs; + +static void +repo_and_refs_clear (RepoAndRefs *data) +{ + g_hash_table_unref (data->refs); + g_object_unref (data->repo); + g_free (data->ordering_name); +} + +static gint +repo_and_refs_compare (gconstpointer a, + gconstpointer b) +{ + const RepoAndRefs *_a = a; + const RepoAndRefs *_b = b; + + return strcmp (_a->ordering_name, _b->ordering_name); +} + +/* Check whether the repo at @dfd/@path is within the given mount, is not equal + * to the @parent_repo, and can be opened. If so, return it as @out_repo and + * all its collection–refs as @out_refs, to be added into the results. */ +static gboolean +scan_repo (int dfd, + const char *path, + const char *mount_name, + const struct stat *mount_root_stbuf, + OstreeRepo *parent_repo, + OstreeRepo **out_repo, + GHashTable **out_refs, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GError) local_error = NULL; + + g_autoptr(OstreeRepo) repo = ostree_repo_open_at (dfd, path, cancellable, &local_error); + if (repo == NULL) + { + g_debug ("Ignoring repository ‘%s’ on mount ‘%s’ as it could not be opened: %s", + path, mount_name, local_error->message); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + int repo_dfd = ostree_repo_get_dfd (repo); + struct stat stbuf; + + if (!glnx_fstat (repo_dfd, &stbuf, &local_error)) + { + g_debug ("Ignoring repository ‘%s’ on mount ‘%s’ as querying its info failed: %s", + path, mount_name, local_error->message); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + /* 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 repository ‘%s’ on mount ‘%s’ as it’s on a different file system from the mount", + path, mount_name); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + /* Exclude repositories which resolve to @parent_repo. */ + if (stbuf.st_dev == parent_repo->device && + stbuf.st_ino == parent_repo->inode) + { + g_debug ("Ignoring repository ‘%s’ on mount ‘%s’ as it is the same as the one we are resolving", + path, mount_name); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + /* List the repo’s refs and return them. */ + g_autoptr(GHashTable) repo_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + + if (!ostree_repo_list_collection_refs (repo, NULL, &repo_refs, + OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES, + cancellable, &local_error)) + { + g_debug ("Ignoring repository ‘%s’ on mount ‘%s’ as its refs could not be listed: %s", + path, mount_name, local_error->message); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + if (out_repo != NULL) + *out_repo = g_steal_pointer (&repo); + if (out_refs != NULL) + *out_refs = g_steal_pointer (&repo_refs); + + return TRUE; +} + +static void +scan_and_add_repo (int dfd, + const char *path, + gboolean sortable, + const char *mount_name, + const struct stat *mount_root_stbuf, + OstreeRepo *parent_repo, + GArray *inout_repos_refs, + GCancellable *cancellable) +{ + g_autoptr(GHashTable) repo_refs = NULL; + g_autoptr(OstreeRepo) repo = NULL; + + if (scan_repo (dfd, path, + mount_name, mount_root_stbuf, + parent_repo, &repo, &repo_refs, cancellable, NULL)) + { + RepoAndRefs val = { + sortable ? g_strdup (path) : NULL, + g_steal_pointer (&repo), + g_steal_pointer (&repo_refs) + }; + g_array_append_val (inout_repos_refs, val); + + g_debug ("%s: Adding repo ‘%s’ (%ssortable)", + G_STRFUNC, path, sortable ? "" : "not "); + } +} + static void ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finder, const OstreeCollectionRef * const *refs, @@ -214,7 +346,6 @@ ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finde 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); @@ -225,18 +356,6 @@ ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finde 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). */ @@ -247,6 +366,64 @@ ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finde continue; } + /* Check if it contains a .ostree/repos.d directory. If not, move on and + * try the other well-known subdirectories. */ + if (!glnx_opendirat (mount_root_dfd, ".ostree/repos.d", TRUE, &repos_dfd, NULL)) + repos_dfd = -1; + + /* List all the repositories in the repos.d directory. */ + /* (element-type GHashTable (element-type OstreeCollectionRef utf8)) */ + g_autoptr(GArray) repos_refs = g_array_new (FALSE, TRUE, sizeof (RepoAndRefs)); + g_array_set_clear_func (repos_refs, (GDestroyNotify) repo_and_refs_clear); + + GLnxDirFdIterator repos_iter; + + if (repos_dfd >= 0 && + !glnx_dirfd_iterator_init_at (repos_dfd, ".", TRUE, &repos_iter, &local_error)) + { + g_debug ("Error iterating over ‘%s/.ostree/repos.d’ directory in mount ‘%s’: %s", + mount_root_path, mount_name, local_error->message); + g_clear_error (&local_error); + /* don’t skip this mount as there’s still the ostree/repo directory to try */ + } + else if (repos_dfd >= 0) + { + while (TRUE) + { + struct dirent *repo_dent; + + if (!glnx_dirfd_iterator_next_dent (&repos_iter, &repo_dent, cancellable, &local_error)) + { + g_debug ("Error iterating over ‘%s/.ostree/repos.d’ directory in mount ‘%s’: %s", + mount_root_path, mount_name, local_error->message); + g_clear_error (&local_error); + /* don’t skip this mount as there’s still the ostree/repo directory to try */ + break; + } + + if (repo_dent == NULL) + break; + + /* Grab the set of collection–refs from the repo if we can open it. */ + scan_and_add_repo (repos_dfd, repo_dent->d_name, TRUE, + mount_name, &mount_root_stbuf, + parent_repo, repos_refs, cancellable); + } + } + + /* Sort the repos lexically. */ + g_array_sort (repos_refs, repo_and_refs_compare); + + /* Also check the .ostree/repo and ostree/repo directories in the mount, + * as well-known special cases. Add them after sorting, so they’re always + * last. */ + scan_and_add_repo (mount_root_dfd, ".ostree/repo", FALSE, + mount_name, &mount_root_stbuf, + parent_repo, repos_refs, cancellable); + scan_and_add_repo (mount_root_dfd, "ostree/repo", FALSE, + mount_name, &mount_root_stbuf, + parent_repo, repos_refs, cancellable); + /* 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. @@ -256,122 +433,67 @@ ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finde for (i = 0; refs[i] != NULL; i++) { - struct stat stbuf; - g_autofree gchar *collection_and_ref = NULL; + const OstreeCollectionRef *ref = refs[i]; 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); - - if (!glnx_fstatat (repos_dfd, collection_and_ref, &stbuf, AT_NO_AUTOMOUNT, &local_error)) + for (gsize j = 0; j < repos_refs->len; j++) { - 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, collection_and_ref, local_error->message); - g_clear_error (&local_error); - continue; + const RepoAndRefs *repo_and_refs = &g_array_index (repos_refs, RepoAndRefs, j); + OstreeRepo *repo = repo_and_refs->repo; + GHashTable *repo_refs = repo_and_refs->refs; + g_autofree char *repo_path = g_file_get_path (ostree_repo_get_path (repo)); + + const gchar *checksum = g_hash_table_lookup (repo_refs, ref); + + if (checksum == NULL) + { + g_debug ("Ignoring repository ‘%s’ when looking for ref (%s, %s) on mount ‘%s’ as it doesn’t contain the ref.", + repo_path, ref->collection_id, ref->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, ref->collection_id, + cancellable, &local_error); + + if (keyring == NULL) + { + g_debug ("Ignoring repository ‘%s’ when looking for ref (%s, %s) on mount ‘%s’ due to missing keyring: %s", + repo_path, ref->collection_id, ref->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.d/$something. + * Add it to the results, keyed by the canonicalised repository URI + * to deduplicate the results. */ + g_autofree char *canonical_repo_path = realpath (repo_path, NULL); + resolved_repo_uri = g_strconcat ("file://", canonical_repo_path, NULL); + g_debug ("Resolved ref (%s, %s) on mount ‘%s’ to repo URI ‘%s’ with keyring ‘%s’.", + ref->collection_id, ref->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) ref, g_strdup (checksum)); + + /* We’ve found a result for this collection–ref. No point in checking + * the other repos on the mount, since pulling in parallel from them won’t help. */ + break; } - - 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, collection_and_ref, (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. */ - if (stbuf.st_dev == parent_repo->device && - stbuf.st_ino == parent_repo->inode) - { - g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as it is the same as the one we are resolving", - refs[i]->collection_id, refs[i]->ref_name, mount_name); - g_clear_error (&local_error); - continue; - } - - /* Grab the given ref and a checksum for it from the repo, if it appears to be a valid repo */ - g_autoptr(OstreeRepo) repo = ostree_repo_open_at (repos_dfd, collection_and_ref, - cancellable, &local_error); - if (!repo) - { - 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, - OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES, - 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. */ - - g_autofree char *repo_abspath = g_build_filename (mount_root_path, ".ostree/repos", - collection_and_ref, NULL); - /* FIXME - why are we using realpath here? */ - g_autofree char *canonical_repo_dir_path = realpath (repo_abspath, NULL); - 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. */ diff --git a/tests/test-repo-finder-mount.c b/tests/test-repo-finder-mount.c index 8376e51e..2ab08001 100644 --- a/tests/test-repo-finder-mount.c +++ b/tests/test-repo-finder-mount.c @@ -70,8 +70,6 @@ static void teardown (Fixture *fixture, gconstpointer test_data) { - g_autoptr(GError) error = NULL; - /* Recursively remove the temporary directory. */ (void)glnx_tmpdir_delete (&fixture->tmpdir, NULL, NULL); @@ -150,7 +148,7 @@ test_repo_finder_mount_no_mounts (Fixture *fixture, g_main_context_pop_thread_default (context); } -/* Create a .ostree/repos directory under the given @mount_root, or abort. */ +/* Create a .ostree/repos.d directory under the given @mount_root, or abort. */ static gboolean assert_create_repos_dir (Fixture *fixture, const gchar *mount_root_name, @@ -160,7 +158,7 @@ assert_create_repos_dir (Fixture *fixture, 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); + g_autofree gchar *path = g_build_filename (mount_root_name, ".ostree", "repos.d", NULL); glnx_shutil_mkdir_p_at_open (fixture->tmpdir.fd, path, 0700, &repos_dfd, NULL, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) g_clear_error (&error); @@ -227,22 +225,22 @@ assert_create_remote_va (Fixture *fixture, } static OstreeRepo * -assert_create_repo_dir (Fixture *fixture, - int repos_dfd, - GMount *repos_mount, - const OstreeCollectionRef *ref, - gchar **out_uri, +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const char *repo_name, + 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. */ +/* Create a @repo_name 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, +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const char *repo_name, + gchar **out_uri, ...) { glnx_fd_close int ref_dfd = -1; @@ -250,15 +248,14 @@ assert_create_repo_dir (Fixture *fixture, 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); + glnx_shutil_mkdir_p_at_open (repos_dfd, repo_name, 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); + g_autoptr(GFile) repos_dir = g_file_get_child (mount_root, ".ostree/repos.d"); + g_autoptr(GFile) repo_dir = g_file_get_child (repos_dir, repo_name); va_start (args, out_uri); repo = assert_create_remote_va (fixture, repo_dir, args); @@ -269,38 +266,19 @@ assert_create_repo_dir (Fixture *fixture, 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) +/* Create a @repo_name symlink under the given @repos_dfd, pointing to + * @symlink_target_path, or abort. */ +static void +assert_create_repo_symlink (int repos_dfd, + const char *repo_name, + const char *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) + if (TEMP_FAILURE_RETRY (symlinkat (symlink_target_path, repos_dfd, repo_name)) != 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 @@ -350,7 +328,7 @@ test_repo_finder_mount_mixed_mounts (Fixture *fixture, 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; + g_autofree gchar *repo1_ref5_checksum = NULL, *repo2_ref3_checksum = NULL; gsize i; const OstreeCollectionRef ref0 = { "org.example.Collection1", "exampleos/x86_64/ref0" }; const OstreeCollectionRef ref1 = { "org.example.Collection1", "exampleos/x86_64/ref1" }; @@ -373,27 +351,26 @@ test_repo_finder_mount_mixed_mounts (Fixture *fixture, 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, + repo1_repo_a = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, "repo1-repo-a", &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, + repo1_repo_b = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, "repo1-repo-b", &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_repo_symlink (repo1_repos, "repo1-repo-a-alias", "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, + repo2_repo_a = assert_create_repo_dir (fixture, repo2_repos, repo2_mount, "repo2-repo-a", &repo2_repo_a_uri, refs[0], &repo2_ref0_checksum, refs[1], &repo2_ref1_checksum, refs[2], &repo2_ref2_checksum, - refs[3], NULL, + refs[3], &repo2_ref3_checksum, 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], "/"); + assert_create_repo_symlink (repo2_repos, "repo2-repo-a-alias", "repo2-repo-a"); + assert_create_repo_symlink (repo2_repos, "dangling-symlink", "repo2-repo-b"); + assert_create_repo_symlink (repo2_repos, "root", "/"); mounts = g_list_prepend (mounts, non_removable_mount); mounts = g_list_prepend (mounts, no_repos_mount); @@ -456,10 +433,108 @@ test_repo_finder_mount_mixed_mounts (Fixture *fixture, 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_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 4); 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); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[3]), ==, repo2_ref3_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); +} + +/* Test resolving the refs against a mock volume which contains two repositories + * in the default repository paths ostree/repo and .ostree/repo, to check that + * those paths are read */ +static void +test_repo_finder_mount_well_known (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) mount = NULL; + glnx_fd_close int repos = -1; + g_autoptr(OstreeRepo) repo_a = NULL, repo_b = NULL; + g_autofree gchar *repo_a_uri = NULL, *repo_b_uri = NULL; + g_autofree gchar *ref_a_checksum = NULL, *ref_b_checksum = NULL; + gsize i; + const OstreeCollectionRef ref_a = { "org.example.Collection1", "refA" }; + const OstreeCollectionRef ref_b = { "org.example.Collection2", "refB" }; + const OstreeCollectionRef * const refs[] = { &ref_a, &ref_b, 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. */ + assert_create_repos_dir (fixture, "mount", &repos, &mount); + repo_a = assert_create_repo_dir (fixture, repos, mount, "../../ostree/repo", &repo_a_uri, + &ref_a, &ref_a_checksum, + NULL); + repo_b = assert_create_repo_dir (fixture, repos, mount, "../../.ostree/repo", &repo_b_uri, + &ref_b, &ref_b_checksum, + NULL); + assert_create_repo_symlink (repos, "repo-a-alias", "../../ostree/repo"); + + mounts = g_list_prepend (mounts, 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"); + + /* 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, ==, 2); + + /* Check that the results are correct: 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, repo_a_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, &ref_a), ==, ref_a_checksum); + } + else if (g_strcmp0 (uri, repo_b_uri) == 0 && + g_strcmp0 (keyring, "remote2.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, &ref_b), ==, ref_b_checksum); } else { @@ -482,6 +557,8 @@ int main (int argc, char **argv) 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); + g_test_add ("/repo-finder-mount/well-known", Fixture, NULL, setup, + test_repo_finder_mount_well_known, teardown); return g_test_run(); }