Initial basic static delta code drop

This has a very basic level of functionality (deltas can be generated,
and applied offline).  There is only some stubbed out pull code to
fetch them via HTTP.

But, better to commit this now and improve it from a known starting
point, rather than have it languish in a branch.
This commit is contained in:
Colin Walters committed 2014-02-04 10:31:44 -05:00
1 parent 844c5ea652
commit 2d6374822b
24 files changed
+1890 -43

No files matched your search

@@ -0,0 +1,380 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013,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 <string.h>
#include "ostree-core-private.h"
#include "ostree-repo-private.h"
#include "ostree-repo-static-delta-private.h"
#include "ostree-diff.h"
#include "otutil.h"
#include "ostree-varint.h"
typedef struct {
guint64 uncompressed_size;
GPtrArray *objects;
GString *payload;
GString *operations;
} OstreeStaticDeltaPartBuilder;
typedef struct {
GPtrArray *parts;
} OstreeStaticDeltaBuilder;
static void
ostree_static_delta_part_builder_unref (OstreeStaticDeltaPartBuilder *part_builder)
{
if (part_builder->objects)
g_ptr_array_unref (part_builder->objects);
if (part_builder->payload)
g_string_free (part_builder->payload, TRUE);
if (part_builder->operations)
g_string_free (part_builder->operations, TRUE);
g_free (part_builder);
}
static OstreeStaticDeltaPartBuilder *
allocate_part (OstreeStaticDeltaBuilder *builder)
{
OstreeStaticDeltaPartBuilder *part = g_new0 (OstreeStaticDeltaPartBuilder, 1);
part->objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
part->payload = g_string_new (NULL);
part->operations = g_string_new (NULL);
part->uncompressed_size = 0;
g_ptr_array_add (builder->parts, part);
return part;
}
static GBytes *
objtype_checksum_array_new (GPtrArray *objects)
{
guint i;
GByteArray *ret = g_byte_array_new ();
g_assert (objects->len > 0);
for (i = 0; i < objects->len; i++)
{
GVariant *serialized_key = objects->pdata[i];
OstreeObjectType objtype;
const char *checksum;
guint8 csum[32];
guint8 objtype_v;
ostree_object_name_deserialize (serialized_key, &checksum, &objtype);
objtype_v = (guint8) objtype;
ostree_checksum_inplace_to_bytes (checksum, csum);
g_byte_array_append (ret, &objtype_v, 1);
g_byte_array_append (ret, csum, sizeof (csum));
}
return g_byte_array_free_to_bytes (ret);
}
static gboolean
generate_delta_lowlatency (OstreeRepo *repo,
const char *from,
const char *to,
OstreeStaticDeltaBuilder *builder,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GHashTableIter hashiter;
gpointer key, value;
OstreeStaticDeltaPartBuilder *current_part = NULL;
gs_unref_object GFile *root_from = NULL;
gs_unref_object GFile *root_to = NULL;
gs_unref_ptrarray GPtrArray *modified = NULL;
gs_unref_ptrarray GPtrArray *removed = NULL;
gs_unref_ptrarray GPtrArray *added = NULL;
gs_unref_hashtable GHashTable *to_reachable_objects = NULL;
gs_unref_hashtable GHashTable *from_reachable_objects = NULL;
gs_unref_hashtable GHashTable *new_reachable_objects = NULL;
if (!ostree_repo_read_commit (repo, from, &root_from, NULL,
cancellable, error))
goto out;
if (!ostree_repo_read_commit (repo, to, &root_to, NULL,
cancellable, error))
goto out;
/* Gather a filesystem level diff; when we do heuristics to ship
* just parts of changed files, we can make use of this data.
*/
modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
if (!ostree_diff_dirs (OSTREE_DIFF_FLAGS_NONE, root_from, root_to, modified, removed, added,
cancellable, error))
goto out;
if (!ostree_repo_traverse_commit (repo, from, -1, &from_reachable_objects,
cancellable, error))
goto out;
if (!ostree_repo_traverse_commit (repo, to, -1, &to_reachable_objects,
cancellable, error))
goto out;
new_reachable_objects = ostree_repo_traverse_new_reachable ();
g_hash_table_iter_init (&hashiter, to_reachable_objects);
while (g_hash_table_iter_next (&hashiter, &key, &value))
{
GVariant *serialized_key = key;
if (g_hash_table_contains (from_reachable_objects, serialized_key))
continue;
g_hash_table_insert (new_reachable_objects, g_variant_ref (serialized_key), serialized_key);
}
current_part = allocate_part (builder);
g_hash_table_iter_init (&hashiter, new_reachable_objects);
while (g_hash_table_iter_next (&hashiter, &key, &value))
{
GVariant *serialized_key = key;
const char *checksum;
OstreeObjectType objtype;
guint64 content_size;
gsize object_payload_start;
gs_unref_object GInputStream *content_stream = NULL;
gsize bytes_read;
const guint readlen = 4096;
ostree_object_name_deserialize (serialized_key, &checksum, &objtype);
if (!ostree_repo_load_object_stream (repo, objtype, checksum,
&content_stream, &content_size,
cancellable, error))
goto out;
current_part->uncompressed_size += content_size;
/* Ensure we have at least one object per delta, even if a given
* object is larger.
*/
if (current_part->objects->len > 0 &&
current_part->payload->len + content_size > OSTREE_STATIC_DELTA_PART_MAX_SIZE_BYTES)
{
current_part = allocate_part (builder);
}
g_ptr_array_add (current_part->objects, g_variant_ref (serialized_key));
object_payload_start = current_part->payload->len;
while (TRUE)
{
gsize empty_space;
empty_space = current_part->payload->allocated_len - current_part->payload->len;
if (empty_space < readlen)
{
gsize origlen;
origlen = current_part->payload->len;
g_string_set_size (current_part->payload, current_part->payload->allocated_len + (readlen - empty_space));
current_part->payload->len = origlen;
}
if (!g_input_stream_read_all (content_stream,
current_part->payload->str + current_part->payload->len,
readlen,
&bytes_read,
cancellable, error))
goto out;
if (bytes_read == 0)
break;
current_part->payload->len += bytes_read;
}
g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_WRITE);
_ostree_write_varuint64 (current_part->operations, object_payload_start);
_ostree_write_varuint64 (current_part->operations, content_size);
g_printerr ("write %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT "\n", (guint64) object_payload_start, (guint64)(content_size));
g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_CLOSE);
}
ret = TRUE;
out:
return ret;
}
/**
* ostree_repo_static_delta_generate:
* @self: Repo
* @opt: High level optimization choice
* @from: ASCII SHA256 checksum of origin
* @to: ASCII SHA256 checksum of target
* @metadata: (allow-none): Optional metadata
* @cancellable: Cancellable
* @error: Error
*
* Generate a lookaside "static delta" from @from which can generate
* the objects in @to. This delta is an optimization over fetching
* individual objects, and can be conveniently stored and applied
* offline.
*/
gboolean
ostree_repo_static_delta_generate (OstreeRepo *self,
OstreeStaticDeltaGenerateOpt opt,
const char *from,
const char *to,
GVariant *metadata,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
OstreeStaticDeltaBuilder builder = { 0, };
guint i;
GVariant *metadata_source;
gs_unref_variant_builder GVariantBuilder *part_headers = NULL;
gs_unref_ptrarray GPtrArray *part_tempfiles = NULL;
gs_unref_variant GVariant *delta_descriptor = NULL;
gs_free char *descriptor_relpath = NULL;
gs_unref_object GFile *descriptor_path = NULL;
gs_unref_object GFile *descriptor_dir = NULL;
gs_unref_variant GVariant *tmp_metadata = NULL;
builder.parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref);
/* Ignore optimization flags */
if (!generate_delta_lowlatency (self, from, to, &builder,
cancellable, error))
goto out;
part_headers = g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT));
part_tempfiles = g_ptr_array_new_with_free_func (g_object_unref);
for (i = 0; i < builder.parts->len; i++)
{
OstreeStaticDeltaPartBuilder *part_builder = builder.parts->pdata[i];
GBytes *payload_b;
GBytes *operations_b;
gs_free guchar *part_checksum = NULL;
gs_free_checksum GChecksum *checksum = NULL;
gs_unref_bytes GBytes *objtype_checksum_array = NULL;
gs_unref_bytes GBytes *checksum_bytes = NULL;
gs_unref_object GFile *part_tempfile = NULL;
gs_unref_object GOutputStream *part_temp_outstream = NULL;
gs_unref_object GInputStream *part_in = NULL;
gs_unref_object GInputStream *part_payload_in = NULL;
gs_unref_object GMemoryOutputStream *part_payload_out = NULL;
gs_unref_object GConverterOutputStream *part_payload_compressor = NULL;
gs_unref_object GConverter *zlib_compressor = NULL;
gs_unref_variant GVariant *delta_part_content = NULL;
gs_unref_variant GVariant *delta_part = NULL;
gs_unref_variant GVariant *delta_part_header = NULL;
payload_b = g_string_free_to_bytes (part_builder->payload);
part_builder->payload = NULL;
operations_b = g_string_free_to_bytes (part_builder->operations);
part_builder->operations = NULL;
/* FIXME - avoid duplicating memory here */
delta_part_content = g_variant_new ("(@ay@ay)",
ot_gvariant_new_ay_bytes (payload_b),
ot_gvariant_new_ay_bytes (operations_b));
g_variant_ref_sink (delta_part_content);
/* Hardcode gzip for now */
zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, 9);
part_payload_in = ot_variant_read (delta_part_content);
part_payload_out = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
part_payload_compressor = (GConverterOutputStream*)g_converter_output_stream_new ((GOutputStream*)part_payload_out, zlib_compressor);
if (0 > g_output_stream_splice ((GOutputStream*)part_payload_compressor, part_payload_in,
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET | G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
cancellable, error))
goto out;
/* FIXME - avoid duplicating memory here */
delta_part = g_variant_new ("(y@ay)",
(guint8)'g',
ot_gvariant_new_ay_bytes (g_memory_output_stream_steal_as_bytes (part_payload_out)));
if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644,
&part_tempfile, &part_temp_outstream,
cancellable, error))
goto out;
part_in = ot_variant_read (delta_part);
if (!ot_gio_splice_get_checksum (part_temp_outstream, part_in,
&part_checksum,
cancellable, error))
goto out;
checksum_bytes = g_bytes_new (part_checksum, 32);
objtype_checksum_array = objtype_checksum_array_new (part_builder->objects);
delta_part_header = g_variant_new ("(@aytt@ay)",
ot_gvariant_new_ay_bytes (checksum_bytes),
g_variant_get_size (delta_part),
part_builder->uncompressed_size,
ot_gvariant_new_ay_bytes (objtype_checksum_array));
g_variant_builder_add_value (part_headers, g_variant_ref (delta_part_header));
g_ptr_array_add (part_tempfiles, g_object_ref (part_tempfile));
}
descriptor_relpath = _ostree_get_relative_static_delta_path (from, to);
descriptor_path = g_file_resolve_relative_path (self->repodir, descriptor_relpath);
descriptor_dir = g_file_get_parent (descriptor_path);
if (!gs_file_ensure_directory (descriptor_dir, TRUE, cancellable, error))
goto out;
for (i = 0; i < builder.parts->len; i++)
{
GFile *tempfile = part_tempfiles->pdata[i];
gs_free char *part_relpath = _ostree_get_relative_static_delta_part_path (from, to, i);
gs_unref_object GFile *part_path = g_file_resolve_relative_path (self->repodir, part_relpath);
if (!gs_file_rename (tempfile, part_path, cancellable, error))
goto out;
}
if (metadata != NULL)
metadata_source = metadata;
else
{
GVariantBuilder tmpbuilder;
g_variant_builder_init (&tmpbuilder, G_VARIANT_TYPE ("(a(ss)a(say))"));
g_variant_builder_add (&tmpbuilder, "a(ss)", NULL);
g_variant_builder_add (&tmpbuilder, "a(say)", NULL);
tmp_metadata = g_variant_builder_end (&tmpbuilder);
g_variant_ref_sink (tmp_metadata);
metadata_source = tmp_metadata;
}
delta_descriptor = g_variant_new ("(@(a(ss)a(say))aya(ayttay))",
metadata_source,
g_variant_builder_new (G_VARIANT_TYPE ("ay")),
part_headers);
if (!ot_util_variant_save (descriptor_path, delta_descriptor, cancellable, error))
goto out;
ret = TRUE;
out:
g_clear_pointer (&builder.parts, g_ptr_array_unref);
return ret;
}