lib/repo-finder-mount: Change the schema for finding repos on volumes

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 <withnall@endlessm.com>

https://github.com/ostreedev/ostree/issues/1174

Closes: #1179
Approved by: cgwalters
This commit is contained in:
Philip Withnall 2017-09-15 15:59:32 +01:00 committed by Atomic Bot
parent 981eb6c226
commit 15247641d9
2 changed files with 388 additions and 189 deletions

View File

@ -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 volumes 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 volumes 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 collectionrefs 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 its 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 repos 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 doesnt exist.",
mount_name, mount_root_path);
else
g_debug ("Ignoring mount %s as %s/.ostree/repos directory cant 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);
/* dont skip this mount as theres 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);
/* dont skip this mount as theres still the ostree/repo directory to try */
break;
}
if (repo_dent == NULL)
break;
/* Grab the set of collectionrefs 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 theyre 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 were looking
* for. If so, and its 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 doesnt 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));
/* Weve found a result for this collectionref. No point in checking
* the other repos on the mount, since pulling in parallel from them wont 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 its 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 doesnt 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. */

View File

@ -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 its on a separate
* file system from /tmp, so its 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();
}