summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2014-08-18 17:29:33 -0400
committerColin Walters <walters@verbum.org>2014-08-18 17:29:33 -0400
commit0882a36e56f80dd5bc1e30c99292834432e87769 (patch)
tree8de730e6e1e783bba6300c1dab5db3da1122b73f
parent89ff7fb617f44e0ccf5b5d9e1df284d8959787d7 (diff)
downloadostree-0882a36e56f80dd5bc1e30c99292834432e87769.tar.gz
A bit more metalink work
-rw-r--r--Makefile-ostree.am1
-rw-r--r--src/libostree/ostree-metalink.c219
-rw-r--r--src/libostree/ostree-metalink.h10
-rw-r--r--src/libostree/ostree-repo-pull.c2
-rw-r--r--src/libostree/ostree-repo.c9
-rw-r--r--src/libotutil/ot-checksum-utils.c26
-rw-r--r--src/libotutil/ot-checksum-utils.h5
-rw-r--r--src/ostree/main.c1
-rw-r--r--src/ostree/ot-builtin-summary.c64
-rw-r--r--src/ostree/ot-builtins.h1
-rwxr-xr-xtests/test-pull-metalink.sh68
11 files changed, 358 insertions, 48 deletions
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
index f1381acd..76df368b 100644
--- a/Makefile-ostree.am
+++ b/Makefile-ostree.am
@@ -40,6 +40,7 @@ ostree_SOURCES = src/ostree/main.c \
src/ostree/ot-builtin-remote.c \
src/ostree/ot-builtin-reset.c \
src/ostree/ot-builtin-rev-parse.c \
+ src/ostree/ot-builtin-summary.c \
src/ostree/ot-builtin-show.c \
src/ostree/ot-builtin-static-delta.c \
src/ostree/ot-main.h \
diff --git a/src/libostree/ostree-metalink.c b/src/libostree/ostree-metalink.c
index d84a5775..2c0541ed 100644
--- a/src/libostree/ostree-metalink.c
+++ b/src/libostree/ostree-metalink.c
@@ -36,25 +36,29 @@ typedef enum {
OSTREE_METALINK_STATE_RESOURCES,
OSTREE_METALINK_STATE_URL,
- OSTREE_METALINK_STATE_PASSTHROUGH, /* Ignoring unknown elements */
- OSTREE_METALINK_STATE_ERROR
+ OSTREE_METALINK_STATE_PASSTHROUGH /* Ignoring unknown elements */
} OstreeMetalinkState;
struct OstreeMetalink
{
GObject parent_instance;
- OstreeMetalink *fetcher;
+ SoupURI *uri;
+
+ OstreeFetcher *fetcher;
char *requested_file;
guint64 max_size;
};
G_DEFINE_TYPE (OstreeMetalink, _ostree_metalink, G_TYPE_OBJECT)
-struct OstreeMetalinkRequest
+typedef struct
{
OstreeMetalink *metalink;
+ GTask *task;
+ GMarkupParseContext *parser;
+
guint passthrough_depth;
OstreeMetalinkState passthrough_previous;
@@ -62,14 +66,20 @@ struct OstreeMetalinkRequest
guint found_our_file_element : 1;
guint verification_known : 1;
+ GChecksumType in_verification_type;
+
guint64 size;
char *verification_sha256;
char *verification_sha512;
+ GFile *result;
+
+ char *last_metalink_error;
+ guint current_url_index;
GPtrArray *urls;
OstreeMetalinkState state;
-}
+} OstreeMetalinkRequest;
static void
state_transition (OstreeMetalinkRequest *self,
@@ -116,7 +126,7 @@ metalink_parser_start (GMarkupParseContext *context,
/* If we've already processed a <file> element we're OK with, just
* ignore the others.
*/
- if (self->urls->length > 0)
+ if (self->urls->len > 0)
{
state_transition (self, OSTREE_METALINK_STATE_PASSTHROUGH);
}
@@ -124,7 +134,6 @@ metalink_parser_start (GMarkupParseContext *context,
{
const char *file_name;
- g_clear_pointer (&self->file_name, g_free);
if (!g_markup_collect_attributes (element_name,
attribute_names,
attribute_values,
@@ -137,7 +146,7 @@ metalink_parser_start (GMarkupParseContext *context,
self->found_a_file_element = TRUE;
- if (strcmp (file_name, self->requested_file) != 0)
+ if (strcmp (file_name, self->metalink->requested_file) != 0)
{
state_transition (self, OSTREE_METALINK_STATE_PASSTHROUGH);
g_assert (self->passthrough_depth == 0);
@@ -183,9 +192,9 @@ metalink_parser_start (GMarkupParseContext *context,
/* Only accept sha256/sha512 */
self->verification_known = TRUE;
if (strcmp (verification_type_str, "sha256") == 0)
- self->verification_type = G_CHECKSUM_SHA256;
+ self->in_verification_type = G_CHECKSUM_SHA256;
else if (strcmp (verification_type_str, "sha512") == 0)
- self->verification_type = G_CHECKSUM_SHA512;
+ self->in_verification_type = G_CHECKSUM_SHA512;
else
self->verification_known = FALSE;
}
@@ -231,7 +240,6 @@ metalink_parser_start (GMarkupParseContext *context,
}
else
unknown_element (self, element_name, error);
- }
break;
case OSTREE_METALINK_STATE_URL:
unknown_element (self, element_name, error);
@@ -251,7 +259,6 @@ metalink_parser_end (GMarkupParseContext *context,
gpointer user_data,
GError **error)
{
- OstreeMetalinkRequest *self = user_data;
}
static void
@@ -280,9 +287,11 @@ metalink_parser_text (GMarkupParseContext *context,
}
break;
case OSTREE_METALINK_STATE_VERIFICATION:
+ break;
+ case OSTREE_METALINK_STATE_HASH:
if (self->verification_known)
{
- switch (self->verification_type)
+ switch (self->in_verification_type)
{
case G_CHECKSUM_SHA256:
self->verification_sha256 = g_strndup (text, text_len);
@@ -295,12 +304,6 @@ metalink_parser_text (GMarkupParseContext *context,
}
}
break;
- case OSTREE_METALINK_STATE_HASH:
- {
- g_clear_pointer (&self->verification_value, g_free);
- self->verification_value = g_strndup (text, text_len);
- }
- break;
case OSTREE_METALINK_STATE_RESOURCES:
break;
case OSTREE_METALINK_STATE_URL:
@@ -314,8 +317,7 @@ metalink_parser_text (GMarkupParseContext *context,
case OSTREE_METALINK_STATE_PASSTHROUGH:
break;
}
- out:
- return;
+
}
static void
@@ -325,6 +327,9 @@ _ostree_metalink_finalize (GObject *object)
self = OSTREE_METALINK (object);
+ g_free (self->requested_file);
+ g_clear_object (&self->uri);
+
G_OBJECT_CLASS (_ostree_metalink_parent_class)->finalize (object);
}
@@ -351,10 +356,14 @@ _ostree_metalink_new (OstreeFetcher *fetcher,
self->requested_file = g_strdup (requested_file);
self->max_size = max_size;
+ self->uri = g_object_ref (uri);
return self;
}
+static void
+try_next_url (OstreeMetalinkRequest *self);
+
static gboolean
valid_hex_checksum (const char *s, gsize expected_len)
{
@@ -363,6 +372,108 @@ valid_hex_checksum (const char *s, gsize expected_len)
return len == expected_len && s[len] == '\0';
}
+static void
+on_fetched_url (GObject *src,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+ OstreeMetalinkRequest *self = g_task_get_task_data (task);
+ GError *local_error = NULL;
+ gs_unref_object GFile *result = NULL;
+ gs_unref_object GFileInfo *finfo = NULL;
+
+ result = _ostree_fetcher_request_uri_with_partial_finish ((OstreeFetcher*)src, res, &local_error);
+ if (!result)
+ goto out;
+
+ finfo = g_file_query_info (result, OSTREE_GIO_FAST_QUERYINFO, 0,
+ g_task_get_cancellable (task), &local_error);
+ if (!finfo)
+ goto out;
+
+ if (g_file_info_get_size (finfo) != self->size)
+ {
+ g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Expected size is %" G_GUINT64_FORMAT " bytes but content is %" G_GUINT64_FORMAT " bytes",
+ self->size, g_file_info_get_size (finfo));
+ goto out;
+ }
+
+ if (self->verification_sha512)
+ {
+ gs_free char *actual = ot_checksum_file (result, G_CHECKSUM_SHA512,
+ g_task_get_cancellable (task),
+ &local_error);
+
+ if (!actual)
+ goto out;
+
+ if (strcmp (self->verification_sha512, actual) != 0)
+ {
+ g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Expected checksum is %s but actual is %s",
+ self->verification_sha512, actual);
+ goto out;
+ }
+ }
+
+ if (self->verification_sha256)
+ {
+ gs_free char *actual = ot_checksum_file (result, G_CHECKSUM_SHA256,
+ g_task_get_cancellable (task),
+ &local_error);
+
+ if (!actual)
+ goto out;
+
+ if (strcmp (self->verification_sha256, actual) != 0)
+ {
+ g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Expected checksum is %s but actual is %s",
+ self->verification_sha256, actual);
+ goto out;
+ }
+ }
+
+ out:
+ if (local_error)
+ {
+ g_free (self->last_metalink_error);
+ self->last_metalink_error = g_strdup (local_error->message);
+ g_clear_error (&local_error);
+
+ /* And here we iterate on the next one if we hit an error */
+ self->current_url_index++;
+ try_next_url (self);
+ }
+ else
+ {
+ self->result = g_object_ref (result);
+ g_task_return_boolean (self->task, TRUE);
+ }
+}
+
+static void
+try_next_url (OstreeMetalinkRequest *self)
+{
+ if (self->current_url_index >= self->urls->len)
+ {
+ g_task_return_new_error (self->task, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Exhausted %u metalink targets, last error: %s",
+ self->urls->len, self->last_metalink_error);
+ }
+ else
+ {
+ SoupURI *next = self->urls->pdata[self->current_url_index];
+
+ _ostree_fetcher_request_uri_with_partial_async (self->metalink->fetcher, next,
+ self->metalink->max_size,
+ g_task_get_cancellable (self->task),
+ on_fetched_url, self->task);
+ }
+}
+
static gboolean
start_target_request_phase (OstreeMetalinkRequest *self,
GError **error)
@@ -404,13 +515,14 @@ start_target_request_phase (OstreeMetalinkRequest *self,
goto out;
}
- if (self->urls->length == 0)
+ if (self->urls->len == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No <url method='http'> elements found");
goto out;
}
+ try_next_url (self);
ret = TRUE;
out:
@@ -430,20 +542,22 @@ on_metalink_bytes_read (GObject *src,
const guint8 *data;
bytes = g_input_stream_read_bytes_finish ((GInputStream*)src,
- result, error);
+ result, &local_error);
if (!bytes)
goto out;
- data = g_bytes_get_data (bytes,
+ data = g_bytes_get_data (bytes, &len);
- if (g_bytes_get_size (bytes) == 0)
+ if (len == 0)
{
if (!start_target_request_phase (self, &local_error))
goto out;
}
else
{
- g_markup_parse_context_parse (self->parser, g_
+ if (!g_markup_parse_context_parse (self->parser, (const char*)data, len, &local_error))
+ goto out;
+
g_input_stream_read_bytes_async ((GInputStream*)src, 8192, G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
on_metalink_bytes_read, task);
@@ -463,7 +577,7 @@ on_retrieved_metalink (GObject *src,
GTask *task = user_data;
gs_unref_object GInputStream *metalink_stream = NULL;
- metalink_stream = ostree_fetcher_stream_uri_finish ((OstreeFetcher*)src, result, &local_error);
+ metalink_stream = _ostree_fetcher_stream_uri_finish ((OstreeFetcher*)src, result, &local_error);
if (!metalink_stream)
goto out;
@@ -481,10 +595,19 @@ ostree_metalink_request_unref (gpointer data)
{
OstreeMetalinkRequest *request = data;
g_object_unref (request->metalink);
+ g_clear_object (&request->result);
+ g_free (request->last_metalink_error);
g_ptr_array_unref (request->urls);
g_free (request);
}
+static const GMarkupParser metalink_parser = {
+ metalink_parser_start,
+ metalink_parser_end,
+ metalink_parser_text,
+ NULL,
+ NULL
+};
void
_ostree_metalink_request_async (OstreeMetalink *self,
@@ -494,8 +617,13 @@ _ostree_metalink_request_async (OstreeMetalink *self,
{
GTask *task = g_task_new (self, cancellable, callback, user_data);
OstreeMetalinkRequest *request = g_new0 (OstreeMetalinkRequest, 1);
+
request->metalink = g_object_ref (self);
request->urls = g_ptr_array_new_with_free_func (g_free);
+ request->task = task; /* Unowned */
+
+ request->parser = g_markup_parse_context_new (&metalink_parser, G_MARKUP_PREFIX_ERROR_POSITION, task, NULL);
+
g_task_set_task_data (task, request, ostree_metalink_request_unref);
_ostree_fetcher_stream_uri_async (self->fetcher, self->uri,
self->max_size, cancellable,
@@ -503,22 +631,36 @@ _ostree_metalink_request_async (OstreeMetalink *self,
}
gboolean
-ostree_metalink_request_finish (OstreeMetalink *self,
- GAsyncResult *result,
- SoupURI **out_target_uri,
- GFile **out_data,
- GError **error)
+_ostree_metalink_request_finish (OstreeMetalink *self,
+ GAsyncResult *result,
+ SoupURI **out_target_uri,
+ GFile **out_data,
+ GError **error)
{
+ OstreeMetalinkRequest *request;
+
+ g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+ request = g_task_get_task_data ((GTask*)result);
+
+ if (g_task_propagate_boolean ((GTask*)result, error))
+ {
+ *out_target_uri = request->urls->pdata[request->current_url_index];
+ *out_data = g_object_ref (request->result);
+ return TRUE;
+ }
+ else
+ return FALSE;
}
-struct MetalinkSyncCallState
+typedef struct
{
gboolean running;
gboolean success;
SoupURI **out_target_uri;
GFile **out_data;
GError **error;
-}
+} MetalinkSyncCallState;
static void
on_async_result (GObject *src,
@@ -527,17 +669,17 @@ on_async_result (GObject *src,
{
MetalinkSyncCallState *state = user_data;
- state->success = ostree_metalink_request_finish ((OstreeMetalink*)src, result,
- state->out_target_uri, state->out_data,
- state->error);
+ state->success = _ostree_metalink_request_finish ((OstreeMetalink*)src, result,
+ state->out_target_uri, state->out_data,
+ state->error);
state->running = FALSE;
}
gboolean
_ostree_metalink_request_sync (OstreeMetalink *self,
- GCancellable *cancellable,
SoupURI **out_target_uri,
GFile **out_data,
+ GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
@@ -550,7 +692,6 @@ _ostree_metalink_request_sync (OstreeMetalink *self,
g_main_context_iteration (sync_context, TRUE);
ret = state.success;
- out:
if (sync_context)
g_main_context_unref (sync_context);
return ret;
diff --git a/src/libostree/ostree-metalink.h b/src/libostree/ostree-metalink.h
index 72bdfb5e..09065b6a 100644
--- a/src/libostree/ostree-metalink.h
+++ b/src/libostree/ostree-metalink.h
@@ -60,11 +60,11 @@ void _ostree_metalink_request_async (OstreeMetalink *self,
GAsyncReadyCallback callback,
gpointer user_data);
-gboolean_ostree_metalink_request_finish (OstreeMetalink *self,
- GAsyncResult *result,
- SoupURI **out_target_uri,
- GFile **out_data,
- GError **error);
+gboolean _ostree_metalink_request_finish (OstreeMetalink *self,
+ GAsyncResult *result,
+ SoupURI **out_target_uri,
+ GFile **out_data,
+ GError **error);
G_END_DECLS
diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c
index 7a0917b8..000a2258 100644
--- a/src/libostree/ostree-repo-pull.c
+++ b/src/libostree/ostree-repo-pull.c
@@ -26,7 +26,7 @@
#include "ostree-core-private.h"
#include "ostree-repo-private.h"
#include "ostree-repo-static-delta-private.h"
-#include "ostree-fetcher.h"
+#include "ostree-metalink.h"
#include "otutil.h"
typedef struct {
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index a16b0496..ec281a4a 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -379,7 +379,7 @@ GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, local_keyfile_unref, g_key_file_unref)
* ostree_repo_remote_add:
* @self: Repo
* @name: Name of remote
- * @url: URL for remote
+ * @url: URL for remote (if URL begins with metalink=, it will be used as such)
* @options: (allow-none): GVariant of type a{sv}
* @cancellable: Cancellable
* @error: Error
@@ -444,7 +444,11 @@ ostree_repo_remote_add (OstreeRepo *self,
target_keyfile = ostree_repo_copy_config (self);
}
- g_key_file_set_string (target_keyfile, section, "url", url);
+ if (g_str_has_prefix (url, "metalink="))
+ g_key_file_set_string (target_keyfile, section, "metalink", url + strlen ("metalink="));
+ else
+ g_key_file_set_string (target_keyfile, section, "url", url);
+
if (options)
keyfile_set_from_vardict (target_keyfile, section, options);
@@ -2286,7 +2290,6 @@ ostree_repo_regenerate_summary (OstreeRepo *self,
GList *ordered_keys = NULL;
GList *iter = NULL;
GHashTableIter hashiter;
- gpointer hkey, hvalue;
if (!ostree_repo_list_refs (self, NULL, &refs, cancellable, error))
goto out;
diff --git a/src/libotutil/ot-checksum-utils.c b/src/libotutil/ot-checksum-utils.c
index 6a45c01b..bf9b89fb 100644
--- a/src/libotutil/ot-checksum-utils.c
+++ b/src/libotutil/ot-checksum-utils.c
@@ -139,6 +139,32 @@ ot_gio_checksum_stream (GInputStream *in,
return ot_gio_splice_get_checksum (NULL, in, out_csum, cancellable, error);
}
+char *
+ot_checksum_file (GFile *file,
+ GChecksumType checksum_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GChecksum *checksum = NULL;
+ gs_free gchar *ret = NULL;
+ gs_unref_object GInputStream *in = NULL;
+
+ in = (GInputStream*)g_file_read (file, cancellable, error);
+ if (!in)
+ goto out;
+
+ checksum = g_checksum_new (checksum_type);
+
+ if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error))
+ goto out;
+
+ ret = g_strdup (g_checksum_get_string (checksum));
+ out:
+ g_clear_pointer (&checksum, (GDestroyNotify) g_checksum_free);
+ return ret;
+
+}
+
static void
checksum_stream_thread (GSimpleAsyncResult *result,
GObject *object,
diff --git a/src/libotutil/ot-checksum-utils.h b/src/libotutil/ot-checksum-utils.h
index 7778ed09..eb8bbc04 100644
--- a/src/libotutil/ot-checksum-utils.h
+++ b/src/libotutil/ot-checksum-utils.h
@@ -53,6 +53,11 @@ gboolean ot_gio_checksum_stream (GInputStream *in,
GCancellable *cancellable,
GError **error);
+char * ot_checksum_file (GFile *file,
+ GChecksumType checksum_type,
+ GCancellable *cancellable,
+ GError **error);
+
void ot_gio_checksum_stream_async (GInputStream *in,
int io_priority,
GCancellable *cancellable,
diff --git a/src/ostree/main.c b/src/ostree/main.c
index b16d8c12..e114690d 100644
--- a/src/ostree/main.c
+++ b/src/ostree/main.c
@@ -55,6 +55,7 @@ static OstreeCommand commands[] = {
{ "rev-parse", ostree_builtin_rev_parse, 0 },
{ "show", ostree_builtin_show, 0 },
{ "static-delta", ostree_builtin_static_delta, 0 },
+ { "summary", ostree_builtin_summary, 0 },
#ifdef HAVE_LIBSOUP
{ "trivial-httpd", ostree_builtin_trivial_httpd, OSTREE_BUILTIN_FLAG_NO_REPO },
#endif
diff --git a/src/ostree/ot-builtin-summary.c b/src/ostree/ot-builtin-summary.c
new file mode 100644
index 00000000..17feb344
--- /dev/null
+++ b/src/ostree/ot-builtin-summary.c
@@ -0,0 +1,64 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2014 Colin Walters <walters@verbum.org>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+#include "otutil.h"
+
+static gboolean opt_update;
+
+static GOptionEntry options[] = {
+ { "update", 'u', 0, G_OPTION_ARG_NONE, &opt_update, "Update the summary", NULL },
+ { NULL }
+};
+
+gboolean
+ostree_builtin_summary (int argc, char **argv, OstreeRepo *repo, GCancellable *cancellable, GError **error)
+{
+ gboolean ret = FALSE;
+ GOptionContext *context;
+ gs_unref_ptrarray GPtrArray *delta_names = NULL;
+
+ context = g_option_context_new ("Manage summary metadata");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (opt_update)
+ {
+ if (!ostree_repo_regenerate_summary (repo, cancellable, error))
+ goto out;
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No option specified; use -u to update summary");
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ return ret;
+}
diff --git a/src/ostree/ot-builtins.h b/src/ostree/ot-builtins.h
index fee66f2b..b8b6507f 100644
--- a/src/ostree/ot-builtins.h
+++ b/src/ostree/ot-builtins.h
@@ -46,6 +46,7 @@ BUILTINPROTO(reset);
BUILTINPROTO(fsck);
BUILTINPROTO(show);
BUILTINPROTO(static_delta);
+BUILTINPROTO(summary);
BUILTINPROTO(rev_parse);
BUILTINPROTO(remote);
BUILTINPROTO(write_refs);
diff --git a/tests/test-pull-metalink.sh b/tests/test-pull-metalink.sh
new file mode 100755
index 00000000..fcadd196
--- /dev/null
+++ b/tests/test-pull-metalink.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 Colin Walters <walters@verbum.org>
+#
+# 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.
+
+set -e
+
+. $(dirname $0)/libtest.sh
+
+setup_fake_remote_repo1 "archive-z2"
+
+# And another web server acting as the metalink server
+cd ${test_tmpdir}
+mkdir metalink-data
+cd metalink-data
+ostree trivial-httpd --daemonize -p ${test_tmpdir}/metalink-httpd-port
+metalink_port=$(cat ${test_tmpdir}/metalink-httpd-port)
+echo "http://127.0.0.1:${metalink_port}" > ${test_tmpdir}/metalink-httpd-address
+
+ostree --repo=${test_tmpdir}/ostree-srv/gnomerepo summary -u
+
+summary_path=${test_tmpdir}/ostree-srv/gnomerepo/summary
+
+echo '1..1'
+cd ${test_tmpdir}
+
+cat <<EOF
+<?xml version="1.0" encoding="utf-8"?>
+<metalink version="3.0" xmlns="http://www.metalinker.org/">
+ <files>
+ <file name="summary">
+ <size>$(stat -c '%s' ${summary_path})</size>
+ <verification>
+ <hash type="md5">$(md5sum ${summary_path} | cut -f 1 -d ' ')</hash>
+ <hash type="sha256">$(sha256sum ${summary_path} | cut -f 1 -d ' ')</hash>
+ <hash type="sha512">$(sha512sum ${summary_path} | cut -f 1 -d ' ')</hash>
+ </verification>
+ <resources maxconnections="1">
+ <url protocol="http" type="http" location="US" preference="100" >$(cat httpd-address)</url>
+ </resources>
+ </file>
+ </files>
+</metalink>
+EOF
+
+cd ${test_tmpdir}
+mkdir repo
+${CMD_PREFIX} ostree --repo=repo init
+${CMD_PREFIX} ostree --repo=repo remote add --set=gpg-verify=false origin metalink=$(cat httpd-address)/ostree/gnomerepo
+# Try both syntaxes
+${CMD_PREFIX} ostree --repo=repo pull origin main
+${CMD_PREFIX} ostree --repo=repo pull origin:main
+${CMD_PREFIX} ostree --repo=repo fsck
+echo "ok pull"