diff --git a/app/flatpak-builtins-build-commit-from.c b/app/flatpak-builtins-build-commit-from.c index 52599770..0838219d 100644 --- a/app/flatpak-builtins-build-commit-from.c +++ b/app/flatpak-builtins-build-commit-from.c @@ -57,6 +57,156 @@ static GOptionEntry options[] = { { NULL } }; +#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)" +#define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)" +#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" + +static char * +_ostree_get_relative_static_delta_path (const char *from, + const char *to, + const char *target) +{ + guint8 csum_to[OSTREE_SHA256_DIGEST_LEN]; + char to_b64[44]; + guint8 csum_to_copy[OSTREE_SHA256_DIGEST_LEN]; + GString *ret = g_string_new ("deltas/"); + + ostree_checksum_inplace_to_bytes (to, csum_to); + ostree_checksum_b64_inplace_from_bytes (csum_to, to_b64); + ostree_checksum_b64_inplace_to_bytes (to_b64, csum_to_copy); + + g_assert (memcmp (csum_to, csum_to_copy, OSTREE_SHA256_DIGEST_LEN) == 0); + + if (from != NULL) + { + guint8 csum_from[OSTREE_SHA256_DIGEST_LEN]; + char from_b64[44]; + + ostree_checksum_inplace_to_bytes (from, csum_from); + ostree_checksum_b64_inplace_from_bytes (csum_from, from_b64); + + g_string_append_c (ret, from_b64[0]); + g_string_append_c (ret, from_b64[1]); + g_string_append_c (ret, '/'); + g_string_append (ret, from_b64 + 2); + g_string_append_c (ret, '-'); + } + + g_string_append_c (ret, to_b64[0]); + g_string_append_c (ret, to_b64[1]); + if (from == NULL) + g_string_append_c (ret, '/'); + g_string_append (ret, to_b64 + 2); + + if (target != NULL) + { + g_string_append_c (ret, '/'); + g_string_append (ret, target); + } + + return g_string_free (ret, FALSE); +} + +static GVariant * +new_bytearray (const guchar *data, + gsize len) +{ + gpointer data_copy = g_memdup (data, len); + GVariant *ret = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data_copy, + len, FALSE, g_free, data_copy); + return ret; +} + +static gboolean +rewrite_delta (OstreeRepo *src_repo, + const char *src_commit, + OstreeRepo *dst_repo, + const char *dst_commit, + GVariant *dst_commitv, + const char *from, + GError **error) +{ + g_autoptr(GFile) src_delta_file = NULL; + g_autoptr(GFile) dst_delta_file = NULL; + g_autofree char *src_detached_key = _ostree_get_relative_static_delta_path (from, src_commit, "commitmeta"); + g_autofree char *dst_detached_key = _ostree_get_relative_static_delta_path (from, dst_commit, "commitmeta"); + g_autofree char *src_delta_dir = _ostree_get_relative_static_delta_path (from, src_commit, NULL); + g_autofree char *dst_delta_dir = _ostree_get_relative_static_delta_path (from, dst_commit, NULL); + g_autofree char *src_superblock_path = _ostree_get_relative_static_delta_path (from, src_commit, "superblock"); + g_autofree char *dst_superblock_path = _ostree_get_relative_static_delta_path (from, dst_commit, "superblock"); + GMappedFile *mfile = NULL; + g_auto(GVariantBuilder) superblock_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER; + g_autoptr(GVariant) src_superblock = NULL; + g_autoptr(GVariant) dst_superblock = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GVariant) dst_detached = NULL; + g_autoptr(GVariant) src_metadata = NULL; + g_autoptr(GVariant) src_recurse = NULL; + g_autoptr(GVariant) src_parts = NULL; + g_auto(GVariantDict) dst_metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER; + int i; + + src_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (src_repo), src_superblock_path); + mfile = g_mapped_file_new (flatpak_file_get_path_cached (src_delta_file), FALSE, NULL); + if (mfile == NULL) + return TRUE; /* No superblock, not an error */ + + bytes = g_mapped_file_get_bytes (mfile); + g_mapped_file_unref (mfile); + + src_superblock = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE)); + + src_metadata = g_variant_get_child_value (src_superblock, 0); + src_recurse = g_variant_get_child_value (src_superblock, 5); + src_parts = g_variant_get_child_value (src_superblock, 6); + + if (g_variant_n_children (src_recurse) != 0) + return flatpak_fail (error, "Recursive deltas not supported, ignoring"); + + g_variant_builder_init (&superblock_builder, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT)); + + g_variant_dict_init (&dst_metadata_dict, src_metadata); + g_variant_dict_remove (&dst_metadata_dict, src_detached_key); + if (ostree_repo_read_commit_detached_metadata (dst_repo, dst_commit, &dst_detached, NULL, NULL)) + g_variant_dict_insert_value (&dst_metadata_dict, dst_detached_key, dst_detached); + + g_variant_builder_add_value (&superblock_builder, g_variant_dict_end (&dst_metadata_dict)); + g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 1)); /* timestamp */ + g_variant_builder_add_value (&superblock_builder, from ? ostree_checksum_to_bytes_v (from) : new_bytearray ((guchar *)"", 0)); + g_variant_builder_add_value (&superblock_builder, ostree_checksum_to_bytes_v (dst_commit)); + g_variant_builder_add_value (&superblock_builder, dst_commitv); + g_variant_builder_add_value (&superblock_builder, src_recurse); + g_variant_builder_add_value (&superblock_builder, src_parts); + g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 7)); /* fallback */ + + dst_superblock = g_variant_ref_sink (g_variant_builder_end (&superblock_builder)); + + if (!glnx_shutil_mkdir_p_at (ostree_repo_get_dfd (dst_repo), dst_delta_dir, 0755, NULL, error)) + return FALSE; + + for (i = 0; i < g_variant_n_children (src_parts); i++) + { + g_autofree char *src_part_path = g_strdup_printf ("%s/%d", src_delta_dir, i); + g_autofree char *dst_part_path = g_strdup_printf ("%s/%d", dst_delta_dir, i); + + if (!glnx_file_copy_at (ostree_repo_get_dfd (src_repo), + src_part_path, + NULL, + ostree_repo_get_dfd (dst_repo), + dst_part_path, + GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOXATTRS, + NULL, error)) + return FALSE; + } + + dst_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (dst_repo), dst_superblock_path); + if (!flatpak_variant_save (dst_delta_file, dst_superblock, NULL, error)) + return FALSE; + + return TRUE; +} + + gboolean flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -224,6 +374,7 @@ flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancella g_autoptr(GFile) dst_parent_root = NULL; g_autoptr(GFile) src_ref_root = NULL; g_autoptr(GVariant) src_commitv = NULL; + g_autoptr(GVariant) dst_commitv = NULL; g_autoptr(OstreeMutableTree) mtree = NULL; g_autoptr(GFile) dst_root = NULL; g_autoptr(GVariant) commitv_metadata = NULL; @@ -319,6 +470,9 @@ flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancella g_print ("%s: %s\n", dst_ref, commit_checksum); + if (!ostree_repo_load_commit (dst_repo, commit_checksum, &dst_commitv, NULL, error)) + return FALSE; + /* This doesn't copy the detached metadata. I'm not sure if this is a problem. * The main thing there is commit signatures, and we can't copy those, as the commit hash changes. */ @@ -357,6 +511,23 @@ flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancella { ostree_repo_transaction_set_ref (dst_repo, NULL, dst_ref, commit_checksum); } + + /* Copy + Rewrite any deltas */ + { + const char *from[2]; + gsize j, n_from = 0; + + if (dst_parent != NULL) + from[n_from++] = dst_parent; + from[n_from++] = NULL; + + for (j = 0; j < n_from; j++) + { + g_autoptr(GError) local_error = NULL; + if (!rewrite_delta (src_repo, resolved_ref, dst_repo, commit_checksum, dst_commitv, from[j], &local_error)) + g_debug ("Failed to copy delta: %s", local_error->message); + } + } } if (!ostree_repo_commit_transaction (dst_repo, NULL, cancellable, error)) diff --git a/doc/flatpak-build-commit-from.xml b/doc/flatpak-build-commit-from.xml index c93ac938..11c14b1a 100644 --- a/doc/flatpak-build-commit-from.xml +++ b/doc/flatpak-build-commit-from.xml @@ -61,6 +61,11 @@ autobuilder, and can be properly signed with the official key. + + Any deltas that affect the original commit and that match parent + commits in the destination repository are copied and rewritten + for the new commit id. +