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,333 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 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 "ostree-repo-private.h"
#include "ostree-repo-static-delta-private.h"
#include "otutil.h"
gboolean
_ostree_static_delta_parse_checksum_array (GVariant *array,
guint8 **out_checksums_array,
guint *out_n_checksums,
GError **error)
{
gsize n = g_variant_n_children (array);
guint n_checksums;
n_checksums = n / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
if (G_UNLIKELY(n == 0 ||
n > (G_MAXUINT32/OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) ||
(n_checksums * OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) != n))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid checksum array length %" G_GSIZE_FORMAT, n);
return FALSE;
}
*out_checksums_array = (gpointer)g_variant_get_data (array);
*out_n_checksums = n_checksums;
return TRUE;
}
/**
* ostree_repo_list_static_delta_names:
* @self: Repo
* @out_deltas: (out) (element-type utf8): String name of deltas (checksum-checksum.delta)
* @cancellable: Cancellable
* @error: Error
*
* This function synchronously enumerates all static deltas in the
* repository, returning its result in @out_deltas.
*/
gboolean
ostree_repo_list_static_delta_names (OstreeRepo *self,
GPtrArray **out_deltas,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gs_unref_ptrarray GPtrArray *ret_deltas = NULL;
gs_unref_object GFileEnumerator *dir_enum = NULL;
ret_deltas = g_ptr_array_new_with_free_func (g_free);
if (g_file_query_exists (self->deltas_dir, NULL))
{
dir_enum = g_file_enumerate_children (self->deltas_dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, error);
if (!dir_enum)
goto out;
while (TRUE)
{
GFileInfo *file_info;
GFile *child;
const char *name;
if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
NULL, error))
goto out;
if (file_info == NULL)
break;
if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
continue;
name = gs_file_get_basename_cached (child);
{
gs_unref_object GFile *meta_path = g_file_get_child (child, "meta");
if (g_file_query_exists (meta_path, NULL))
{
g_ptr_array_add (ret_deltas, g_strdup (name));
}
}
}
}
ret = TRUE;
gs_transfer_out_value (out_deltas, &ret_deltas);
out:
return ret;
}
static gboolean
have_all_objects (OstreeRepo *repo,
GVariant *checksum_array,
gboolean *out_have_all,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
guint8 *checksums_data;
guint i,n_checksums;
gboolean have_object = TRUE;
if (!_ostree_static_delta_parse_checksum_array (checksum_array,
&checksums_data,
&n_checksums,
error))
goto out;
for (i = 0; i < n_checksums; i++)
{
guint8 objtype = *checksums_data;
const guint8 *csum = checksums_data + 1;
char tmp_checksum[65];
if (G_UNLIKELY(!ostree_validate_structureof_objtype (objtype, error)))
goto out;
ostree_checksum_inplace_from_bytes (csum, tmp_checksum);
if (!ostree_repo_has_object (repo, (OstreeObjectType) objtype, tmp_checksum,
&have_object, cancellable, error))
goto out;
if (!have_object)
break;
checksums_data += OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
}
ret = TRUE;
*out_have_all = have_object;
out:
return ret;
}
static gboolean
zlib_uncompress_data (GBytes *data,
GBytes **out_uncompressed,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gs_unref_object GMemoryInputStream *memin = (GMemoryInputStream*)g_memory_input_stream_new_from_bytes (data);
gs_unref_object GMemoryOutputStream *memout = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
gs_unref_object GConverter *zlib_decomp =
(GConverter*) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
gs_unref_object GInputStream *convin = g_converter_input_stream_new ((GInputStream*)memin, zlib_decomp);
if (0 > g_output_stream_splice ((GOutputStream*)memout, convin,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
cancellable, error))
goto out;
ret = TRUE;
*out_uncompressed = g_memory_output_stream_steal_as_bytes (memout);
out:
return ret;
}
/**
* ostree_repo_static_delta_execute_offline:
* @self: Repo
* @dir: Path to a directory containing static delta data
* @skip_validation: If %TRUE, assume data integrity
* @cancellable: Cancellable
* @error: Error
*
* Given a directory representing an already-downloaded static delta
* on disk, apply it, generating a new commit. The directory must be
* named with the form "FROM-TO", where both are checksums, and it
* must contain a file named "meta", along with at least one part.
*/
gboolean
ostree_repo_static_delta_execute_offline (OstreeRepo *self,
GFile *dir,
gboolean skip_validation,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
guint i, n;
gs_unref_object GFile *meta_file = g_file_get_child (dir, "meta");
gs_unref_variant GVariant *meta = NULL;
gs_unref_variant GVariant *headers = NULL;
if (!ot_util_variant_map (meta_file, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_META_FORMAT),
FALSE, &meta, error))
goto out;
headers = g_variant_get_child_value (meta, 2);
n = g_variant_n_children (headers);
for (i = 0; i < n; i++)
{
guint64 size;
guint64 usize;
const guchar *csum;
gboolean have_all;
gs_unref_variant GVariant *header = NULL;
gs_unref_variant GVariant *csum_v = NULL;
gs_unref_variant GVariant *objects = NULL;
gs_unref_object GFile *part_path = NULL;
gs_unref_variant GVariant *part = NULL;
gs_unref_object GInputStream *raw_in = NULL;
gs_unref_object GInputStream *in = NULL;
header = g_variant_get_child_value (headers, i);
g_variant_get (header, "(@aytt@ay)", &csum_v, &size, &usize, &objects);
if (!have_all_objects (self, objects, &have_all, cancellable, error))
goto out;
/* If we already have these objects, don't bother executing the
* static delta.
*/
if (have_all)
continue;
csum = ostree_checksum_bytes_peek_validate (csum_v, error);
if (!csum)
goto out;
part_path = ot_gfile_resolve_path_printf (dir, "%u", i);
in = (GInputStream*)g_file_read (part_path, cancellable, error);
if (!in)
goto out;
if (!skip_validation)
{
gs_unref_object GInputStream *tmp_in = NULL;
gs_free guchar *actual_checksum = NULL;
tmp_in = (GInputStream*)g_file_read (part_path, cancellable, error);
if (!tmp_in)
goto out;
if (!ot_gio_checksum_stream (tmp_in, &actual_checksum,
cancellable, error))
goto out;
if (ostree_cmp_checksum_bytes (csum, actual_checksum) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Checksum mismatch in static delta %s part %u",
gs_file_get_path_cached (dir), i);
goto out;
}
}
{
GMappedFile *mfile = gs_file_map_noatime (part_path, cancellable, error);
gs_unref_bytes GBytes *bytes = NULL;
gs_unref_bytes GBytes *payload = NULL;
gsize partlen;
const guint8*partdata;
if (!mfile)
goto out;
bytes = g_mapped_file_get_bytes (mfile);
g_mapped_file_unref (mfile);
partdata = g_bytes_get_data (bytes, &partlen);
if (partlen < 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Corrupted 0 length byte part %s/%i",
gs_file_get_basename_cached (dir),
i);
goto out;
}
switch (partdata[0])
{
case 0:
payload = g_bytes_new_from_bytes (bytes, 1, partlen - 1);
break;
case 'g':
{
gs_unref_bytes GBytes *subbytes = g_bytes_new_from_bytes (bytes, 1, partlen - 1);
if (!zlib_uncompress_data (subbytes, &payload,
cancellable, error))
goto out;
}
break;
}
part = ot_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT),
payload, FALSE);
if (!_ostree_static_delta_part_execute (self, objects, part, cancellable, error))
{
g_prefix_error (error, "executing delta part %i: ", i);
goto out;
}
}
}
ret = TRUE;
out:
return ret;
}