ostree/ostree/ot-builtin-pull.c

352 lines
9.8 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2011 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.
*
* Author: Colin Walters <walters@verbum.org>
*/
#include "config.h"
#include "ot-builtins.h"
#include "ostree.h"
#include <glib/gi18n.h>
#include <libsoup/soup-gnome.h>
static GOptionEntry options[] = {
{ NULL }
};
static gboolean
fetch_uri (OstreeRepo *repo,
SoupSession *soup,
SoupURI *uri,
char **temp_filename,
GError **error)
{
gboolean ret = FALSE;
SoupMessage *msg = NULL;
guint response;
char *template = NULL;
int fd;
SoupBuffer *buf = NULL;
GFile *tempf = NULL;
msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
response = soup_session_send_message (soup, msg);
if (response != 200)
{
char *uri_string = soup_uri_to_string (uri, FALSE);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to retrieve '%s': %d %s",
uri_string, response, msg->reason_phrase);
g_free (uri_string);
goto out;
}
template = g_strdup_printf ("%s/tmp-fetchXXXXXX", ostree_repo_get_path (repo));
fd = g_mkstemp (template);
if (fd < 0)
{
ot_util_set_error_from_errno (error, errno);
goto out;
}
close (fd);
tempf = ot_util_new_file_for_path (template);
buf = soup_message_body_flatten (msg->response_body);
if (!g_file_replace_contents (tempf, buf->data, buf->length, NULL, FALSE, 0, NULL, NULL, error))
goto out;
*temp_filename = template;
template = NULL;
ret = TRUE;
out:
g_free (template);
g_clear_object (&msg);
g_clear_object (&tempf);
return ret;
}
static gboolean
store_object (OstreeRepo *repo,
SoupSession *soup,
SoupURI *baseuri,
const char *object,
OstreeObjectType objtype,
gboolean *did_exist,
GError **error)
{
gboolean ret = FALSE;
char *filename = NULL;
char *objpath = NULL;
char *relpath = NULL;
SoupURI *obj_uri = NULL;
objpath = ostree_get_relative_object_path (object, objtype, TRUE);
obj_uri = soup_uri_copy (baseuri);
relpath = g_build_filename (soup_uri_get_path (obj_uri), objpath, NULL);
soup_uri_set_path (obj_uri, relpath);
if (!fetch_uri (repo, soup, obj_uri, &filename, error))
goto out;
if (!ostree_repo_store_packfile (repo, object, filename, objtype, error))
goto out;
ret = TRUE;
out:
if (obj_uri)
soup_uri_free (obj_uri);
if (filename)
(void) unlink (filename);
g_free (filename);
g_free (objpath);
g_free (relpath);
return ret;
}
static gboolean
store_tree_recurse (OstreeRepo *repo,
SoupSession *soup,
SoupURI *base_uri,
const char *rev,
GError **error)
{
gboolean ret = FALSE;
GVariant *tree = NULL;
GVariant *files_variant = NULL;
GVariant *dirs_variant = NULL;
OstreeSerializedVariantType metatype;
gboolean did_exist;
int i, n;
if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
goto out;
if (!did_exist)
{
if (!ostree_repo_load_variant (repo, rev, &metatype, &tree, error))
goto out;
if (metatype != OSTREE_SERIALIZED_TREE_VARIANT)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Tree metadata '%s' has wrong type %d, expected %d",
rev, metatype, OSTREE_SERIALIZED_TREE_VARIANT);
goto out;
}
/* PARSE OSTREE_SERIALIZED_TREE_VARIANT */
g_variant_get_child (tree, 2, "@a(ss)", &files_variant);
g_variant_get_child (tree, 3, "@a(sss)", &dirs_variant);
n = g_variant_n_children (files_variant);
for (i = 0; i < n; i++)
{
const char *filename;
const char *checksum;
g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
if (!store_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_FILE, &did_exist, error))
goto out;
}
n = g_variant_n_children (dirs_variant);
for (i = 0; i < n; i++)
{
const char *dirname;
const char *tree_checksum;
const char *meta_checksum;
g_variant_get_child (dirs_variant, i, "(sss)",
&dirname, &tree_checksum, &meta_checksum);
if (!store_tree_recurse (repo, soup, base_uri, tree_checksum, error))
goto out;
if (!store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
goto out;
}
}
ret = TRUE;
out:
if (tree)
g_variant_unref (tree);
if (files_variant)
g_variant_unref (files_variant);
if (dirs_variant)
g_variant_unref (dirs_variant);
return ret;
}
static gboolean
store_commit_recurse (OstreeRepo *repo,
SoupSession *soup,
SoupURI *base_uri,
const char *rev,
GError **error)
{
gboolean ret = FALSE;
GVariant *commit = NULL;
OstreeSerializedVariantType metatype;
const char *tree_contents_checksum;
const char *tree_meta_checksum;
gboolean did_exist;
if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
goto out;
if (!did_exist)
{
if (!ostree_repo_load_variant (repo, rev, &metatype, &commit, error))
goto out;
if (metatype != OSTREE_SERIALIZED_COMMIT_VARIANT)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Commit '%s' has wrong type %d, expected %d",
rev, metatype, OSTREE_SERIALIZED_COMMIT_VARIANT);
goto out;
}
/* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */
g_variant_get_child (commit, 6, "&s", &tree_contents_checksum);
g_variant_get_child (commit, 7, "&s", &tree_meta_checksum);
if (!store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
goto out;
if (!store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, error))
goto out;
}
ret = TRUE;
out:
if (commit)
g_variant_unref (commit);
return ret;
}
gboolean
ostree_builtin_pull (int argc, char **argv, const char *repo_path, GError **error)
{
GOptionContext *context;
gboolean ret = FALSE;
OstreeRepo *repo = NULL;
const char *remote;
const char *branch;
char *remote_branch_ref_path = NULL;
char *key = NULL;
char *baseurl = NULL;
char *refpath = NULL;
char *temppath = NULL;
GKeyFile *config = NULL;
SoupURI *base_uri = NULL;
SoupURI *target_uri = NULL;
SoupSession *soup = NULL;
char *rev = NULL;
context = g_option_context_new ("REMOTE BRANCH - Download data from remote repository");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
repo = ostree_repo_new (repo_path);
if (!ostree_repo_check (repo, error))
goto out;
if (argc < 3)
{
ot_util_usage_error (context, "REMOTE and BRANCH must be specified", error);
goto out;
}
remote = argv[1];
branch = argv[2];
config = ostree_repo_get_config (repo);
key = g_strdup_printf ("remote \"%s\"", remote);
baseurl = g_key_file_get_string (config, key, "url", error);
if (!baseurl)
goto out;
base_uri = soup_uri_new (baseurl);
if (!base_uri)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to parse url '%s'", baseurl);
goto out;
}
target_uri = soup_uri_copy (base_uri);
g_free (refpath);
refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", branch, NULL);
soup_uri_set_path (target_uri, refpath);
soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR,
NULL);
if (!fetch_uri (repo, soup, target_uri, &temppath, error))
goto out;
rev = ot_util_get_file_contents_utf8 (temppath, error);
if (!rev)
goto out;
g_strchomp (rev);
if (!ostree_validate_checksum_string (rev, error))
goto out;
if (!store_commit_recurse (repo, soup, base_uri, rev, error))
goto out;
if (!ostree_repo_write_ref (repo, FALSE, branch, rev, error))
goto out;
ret = TRUE;
out:
if (context)
g_option_context_free (context);
if (temppath)
(void) unlink (temppath);
g_free (temppath);
g_free (key);
g_free (rev);
g_free (baseurl);
g_free (refpath);
g_free (remote_branch_ref_path);
g_clear_object (&soup);
if (base_uri)
soup_uri_free (base_uri);
if (target_uri)
soup_uri_free (target_uri);
g_clear_object (&repo);
g_clear_object (&soup);
return ret;
}