From 63c1aace83b13f899e2eae07301d6bf511f71534 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 8 Dec 2016 11:21:56 +0100 Subject: [PATCH] Import OCI initial support to common/* --- common/Makefile.am.inc | 8 +- common/flatpak-json-oci.c | 716 +++++++++++++++++++++ common/flatpak-json-oci.h | 231 +++++++ common/flatpak-json.c | 597 ++++++++++++++++++ common/flatpak-json.h | 92 +++ common/flatpak-oci-registry.c | 1095 +++++++++++++++++++++++++++++++++ common/flatpak-oci-registry.h | 104 ++++ lib/Makefile.am.inc | 1 + 8 files changed, 2843 insertions(+), 1 deletion(-) create mode 100644 common/flatpak-json-oci.c create mode 100644 common/flatpak-json-oci.h create mode 100644 common/flatpak-json.c create mode 100644 common/flatpak-json.h create mode 100644 common/flatpak-oci-registry.c create mode 100644 common/flatpak-oci-registry.h diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 8e81b0ef1..4d09f66b8 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -48,6 +48,12 @@ libflatpak_common_la_SOURCES = \ common/gvdb/gvdb-builder.c \ common/flatpak-db.c \ common/flatpak-db.h \ + common/flatpak-json.c \ + common/flatpak-json.h \ + common/flatpak-json-oci.c \ + common/flatpak-json-oci.h \ + common/flatpak-oci-registry.c \ + common/flatpak-oci-registry.h \ $(NULL) libflatpak_common_la_CFLAGS = \ @@ -61,4 +67,4 @@ libflatpak_common_la_CFLAGS = \ $(LIBSECCOMP_CFLAGS) \ -I$(srcdir)/dbus-proxy \ $(NULL) -libflatpak_common_la_LIBADD = libglnx.la $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) $(XAUTH_LIBS) $(LIBSECCOMP_LIBS) +libflatpak_common_la_LIBADD = libglnx.la $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) $(JSON_LIBS) $(XAUTH_LIBS) $(LIBSECCOMP_LIBS) diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c new file mode 100644 index 000000000..e9064b8ce --- /dev/null +++ b/common/flatpak-json-oci.c @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2015 Red Hat, Inc + * + * This file 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 file 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 program. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" +#include "string.h" + +#include "flatpak-json-oci.h" +#include "flatpak-utils.h" +#include "libglnx.h" + +const char * +flatpak_arch_to_oci_arch (const char *flatpak_arch) +{ + if (strcmp (flatpak_arch, "x86_64") == 0) + return "amd64"; + if (strcmp (flatpak_arch, "aarch64") == 0) + return "arm64"; + if (strcmp (flatpak_arch, "i386") == 0) + return "386"; + return flatpak_arch; +} + +void +flatpak_oci_descriptor_destroy (FlatpakOciDescriptor *self) +{ + g_free (self->mediatype); + g_free (self->digest); + g_strfreev (self->urls); +} + +void +flatpak_oci_descriptor_free (FlatpakOciDescriptor *self) +{ + flatpak_oci_descriptor_destroy (self); + g_free (self); +} + +static FlatpakJsonProp flatpak_oci_descriptor_props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciDescriptor, mediatype, "mediaType"), + FLATPAK_JSON_STRING_PROP (FlatpakOciDescriptor, digest, "digest"), + FLATPAK_JSON_INT64_PROP (FlatpakOciDescriptor, size, "size"), + FLATPAK_JSON_STRV_PROP (FlatpakOciDescriptor, urls, "urls"), + FLATPAK_JSON_LAST_PROP +}; + +static void +flatpak_oci_manifest_platform_destroy (FlatpakOciManifestPlatform *self) +{ + g_free (self->architecture); + g_free (self->os); + g_free (self->os_version); + g_strfreev (self->os_features); + g_free (self->variant); + g_strfreev (self->features); +} + +void +flatpak_oci_manifest_descriptor_destroy (FlatpakOciManifestDescriptor *self) +{ + flatpak_oci_manifest_platform_destroy (&self->platform); + flatpak_oci_descriptor_destroy (&self->parent); +} + +void +flatpak_oci_manifest_descriptor_free (FlatpakOciManifestDescriptor *self) +{ + flatpak_oci_manifest_descriptor_destroy (self); + g_free (self); +} + +static FlatpakJsonProp flatpak_oci_manifest_platform_props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciManifestPlatform, architecture, "architecture"), + FLATPAK_JSON_STRING_PROP (FlatpakOciManifestPlatform, os, "os"), + FLATPAK_JSON_STRING_PROP (FlatpakOciManifestPlatform, os_version, "os.version"), + FLATPAK_JSON_STRING_PROP (FlatpakOciManifestPlatform, variant, "variant"), + FLATPAK_JSON_STRV_PROP (FlatpakOciManifestPlatform, os_features, "os.features"), + FLATPAK_JSON_STRV_PROP (FlatpakOciManifestPlatform, features, "features"), + FLATPAK_JSON_LAST_PROP +}; +static FlatpakJsonProp flatpak_oci_manifest_descriptor_props[] = { + FLATPAK_JSON_PARENT_PROP (FlatpakOciManifestDescriptor, parent, flatpak_oci_descriptor_props), + FLATPAK_JSON_STRUCT_PROP (FlatpakOciManifestDescriptor, platform, "platform", flatpak_oci_manifest_platform_props), + FLATPAK_JSON_LAST_PROP +}; + +G_DEFINE_TYPE (FlatpakOciRef, flatpak_oci_ref, FLATPAK_TYPE_JSON); + +static void +flatpak_oci_ref_finalize (GObject *object) +{ + FlatpakOciRef *self = FLATPAK_OCI_REF (object); + + flatpak_oci_descriptor_destroy (&self->descriptor); + + G_OBJECT_CLASS (flatpak_oci_ref_parent_class)->finalize (object); +} + +static void +flatpak_oci_ref_class_init (FlatpakOciRefClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakJsonClass *json_class = FLATPAK_JSON_CLASS (klass); + static FlatpakJsonProp props[] = { + FLATPAK_JSON_PARENT_PROP (FlatpakOciRef, descriptor, flatpak_oci_descriptor_props), + FLATPAK_JSON_LAST_PROP + }; + + object_class->finalize = flatpak_oci_ref_finalize; + json_class->props = props; + json_class->mediatype = FLATPAK_OCI_MEDIA_TYPE_DESCRIPTOR; +} + +static void +flatpak_oci_ref_init (FlatpakOciRef *self) +{ +} + +FlatpakOciRef * +flatpak_oci_ref_new (const char *mediatype, + const char *digest, + gint64 size) +{ + FlatpakOciRef *ref; + + ref = g_object_new (FLATPAK_TYPE_OCI_REF, NULL); + ref->descriptor.mediatype = g_strdup (mediatype); + ref->descriptor.digest = g_strdup (digest); + ref->descriptor.size = size; + + return ref; +} + +const char * +flatpak_oci_ref_get_mediatype (FlatpakOciRef *self) +{ + return self->descriptor.mediatype; +} + +const char * +flatpak_oci_ref_get_digest (FlatpakOciRef *self) +{ + return self->descriptor.digest; +} + +gint64 +flatpak_oci_ref_get_size (FlatpakOciRef *self) +{ + return self->descriptor.size; +} + +const char ** +flatpak_oci_ref_get_urls (FlatpakOciRef *self) +{ + return (const char **)self->descriptor.urls; +} + +void +flatpak_oci_ref_set_urls (FlatpakOciRef *self, + const char **urls) +{ + g_strfreev (self->descriptor.urls); + self->descriptor.urls = g_strdupv ((char **)urls); +} + +G_DEFINE_TYPE (FlatpakOciVersioned, flatpak_oci_versioned, FLATPAK_TYPE_JSON); + +static void +flatpak_oci_versioned_finalize (GObject *object) +{ + FlatpakOciVersioned *self = FLATPAK_OCI_VERSIONED (object); + + g_free (self->mediatype); + + G_OBJECT_CLASS (flatpak_oci_versioned_parent_class)->finalize (object); +} + +static void +flatpak_oci_versioned_class_init (FlatpakOciVersionedClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakJsonClass *json_class = FLATPAK_JSON_CLASS (klass); + static FlatpakJsonProp props[] = { + FLATPAK_JSON_INT64_PROP (FlatpakOciVersioned, version, "schemaVersion"), + FLATPAK_JSON_STRING_PROP (FlatpakOciVersioned, mediatype, "mediaType"), + FLATPAK_JSON_LAST_PROP + }; + + object_class->finalize = flatpak_oci_versioned_finalize; + json_class->props = props; +} + +static void +flatpak_oci_versioned_init (FlatpakOciVersioned *self) +{ +} + +FlatpakOciVersioned * +flatpak_oci_versioned_from_json (GBytes *bytes, GError **error) +{ + g_autoptr(JsonParser) parser = NULL; + JsonNode *root = NULL; + const gchar *mediatype; + JsonObject *object; + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + error)) + return NULL; + + root = json_parser_get_root (parser); + object = json_node_get_object (root); + + mediatype = json_object_get_string_member (object, "mediaType"); + if (mediatype == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Versioned object lacks mediatype"); + return NULL; + } + + if (strcmp (mediatype, FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST) == 0) + return (FlatpakOciVersioned *) flatpak_json_from_node (root, FLATPAK_TYPE_OCI_MANIFEST, error); + + if (strcmp (mediatype, FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFESTLIST) == 0) + return (FlatpakOciVersioned *) flatpak_json_from_node (root, FLATPAK_TYPE_OCI_MANIFEST_LIST, error); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Unsupported media type %s", mediatype); + return NULL; +} + +const char * +flatpak_oci_versioned_get_mediatype (FlatpakOciVersioned *self) +{ + return self->mediatype; +} + +gint64 +flatpak_oci_versioned_get_version (FlatpakOciVersioned *self) +{ + return self->version; +} + +G_DEFINE_TYPE (FlatpakOciManifest, flatpak_oci_manifest, FLATPAK_TYPE_OCI_VERSIONED); + +static void +flatpak_oci_manifest_finalize (GObject *object) +{ + FlatpakOciManifest *self = (FlatpakOciManifest *) object; + int i; + + for (i = 0; self->layers != NULL && self->layers[i] != NULL; i++) + flatpak_oci_descriptor_free (self->layers[i]); + g_free (self->layers); + flatpak_oci_descriptor_destroy (&self->config); + if (self->annotations) + g_hash_table_destroy (self->annotations); + + G_OBJECT_CLASS (flatpak_oci_manifest_parent_class)->finalize (object); +} + +static void +flatpak_oci_manifest_class_init (FlatpakOciManifestClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakJsonClass *json_class = FLATPAK_JSON_CLASS (klass); + static FlatpakJsonProp props[] = { + FLATPAK_JSON_STRUCT_PROP(FlatpakOciManifest, config, "config", flatpak_oci_descriptor_props), + FLATPAK_JSON_STRUCTV_PROP(FlatpakOciManifest, layers, "layers", flatpak_oci_descriptor_props), + FLATPAK_JSON_STRMAP_PROP(FlatpakOciManifest, annotations, "annotations"), + FLATPAK_JSON_LAST_PROP + }; + + object_class->finalize = flatpak_oci_manifest_finalize; + json_class->props = props; + json_class->mediatype = FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST; +} + +static void +flatpak_oci_manifest_init (FlatpakOciManifest *self) +{ +} + +FlatpakOciManifest * +flatpak_oci_manifest_new (void) +{ + FlatpakOciManifest *manifest; + + manifest = g_object_new (FLATPAK_TYPE_OCI_MANIFEST, NULL); + manifest->parent.version = 2; + manifest->parent.mediatype = g_strdup (FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST); + + manifest->annotations = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + return manifest; +} + +void +flatpak_oci_manifest_set_config (FlatpakOciManifest *self, + FlatpakOciRef *ref) +{ + g_free (self->config.mediatype); + self->config.mediatype = g_strdup (ref->descriptor.mediatype); + g_free (self->config.digest); + self->config.digest = g_strdup (ref->descriptor.digest); + self->config.size = ref->descriptor.size; +} + +static int +ptrv_count (gpointer *ptrs) +{ + int count; + + for (count = 0; ptrs != NULL && ptrs[count] != NULL; count++) + ; + + return count; +} + +void +flatpak_oci_manifest_set_layer (FlatpakOciManifest *self, + FlatpakOciRef *ref) +{ + FlatpakOciRef *refs[2] = { ref, NULL }; + flatpak_oci_manifest_set_layers (self, refs); +} + +void +flatpak_oci_manifest_set_layers (FlatpakOciManifest *self, + FlatpakOciRef **refs) +{ + int i, count; + + for (i = 0; self->layers != NULL && self->layers[i] != NULL; i++) + flatpak_oci_descriptor_free (self->layers[i]); + g_free (self->layers); + + count = ptrv_count ((gpointer *)refs); + + self->layers = g_new0 (FlatpakOciDescriptor *, count + 1); + for (i = 0; i < count; i++) + { + self->layers[i] = g_new0 (FlatpakOciDescriptor, 1); + self->layers[i]->mediatype = g_strdup (refs[i]->descriptor.mediatype); + self->layers[i]->digest = g_strdup (refs[i]->descriptor.digest); + self->layers[i]->size = refs[i]->descriptor.size; + } +} + +int +flatpak_oci_manifest_get_n_layers (FlatpakOciManifest *self) +{ + return ptrv_count ((gpointer *)self->layers); +} + +const char * +flatpak_oci_manifest_get_layer_digest (FlatpakOciManifest *self, + int i) +{ + return self->layers[i]->digest; +} + +GHashTable * +flatpak_oci_manifest_get_annotations (FlatpakOciManifest *self) +{ + return self->annotations; +} + +G_DEFINE_TYPE (FlatpakOciManifestList, flatpak_oci_manifest_list, FLATPAK_TYPE_OCI_VERSIONED); + +static void +flatpak_oci_manifest_list_finalize (GObject *object) +{ + FlatpakOciManifestList *self = (FlatpakOciManifestList *) object; + int i; + + for (i = 0; self->manifests != NULL && self->manifests[i] != NULL; i++) + flatpak_oci_manifest_descriptor_free (self->manifests[i]); + g_free (self->manifests); + + if (self->annotations) + g_hash_table_destroy (self->annotations); + + G_OBJECT_CLASS (flatpak_oci_manifest_list_parent_class)->finalize (object); +} + + +static void +flatpak_oci_manifest_list_class_init (FlatpakOciManifestListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakJsonClass *json_class = FLATPAK_JSON_CLASS (klass); + static FlatpakJsonProp props[] = { + FLATPAK_JSON_STRUCTV_PROP(FlatpakOciManifestList, manifests, "manifests", flatpak_oci_manifest_descriptor_props), + FLATPAK_JSON_STRMAP_PROP(FlatpakOciManifestList, annotations, "annotations"), + FLATPAK_JSON_LAST_PROP + }; + + object_class->finalize = flatpak_oci_manifest_list_finalize; + json_class->props = props; + json_class->mediatype = FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFESTLIST; +} + +static void +flatpak_oci_manifest_list_init (FlatpakOciManifestList *self) +{ +} + +G_DEFINE_TYPE (FlatpakOciImage, flatpak_oci_image, FLATPAK_TYPE_JSON); + +static void +flatpak_oci_image_rootfs_destroy (FlatpakOciImageRootfs *self) +{ + g_free (self->type); + g_strfreev (self->diff_ids); +} + +static void +flatpak_oci_image_config_destroy (FlatpakOciImageConfig *self) +{ + g_free (self->user); + g_free (self->working_dir); + g_strfreev (self->env); + g_strfreev (self->cmd); + g_strfreev (self->entrypoint); + g_strfreev (self->exposed_ports); + g_strfreev (self->volumes); + if (self->labels) + g_hash_table_destroy (self->labels); +} + +static void +flatpak_oci_image_history_free (FlatpakOciImageHistory *self) +{ + g_free (self->created); + g_free (self->created_by); + g_free (self->author); + g_free (self->comment); + g_free (self); +} + +static void +flatpak_oci_image_finalize (GObject *object) +{ + FlatpakOciImage *self = (FlatpakOciImage *) object; + int i; + + g_free (self->created); + g_free (self->author); + g_free (self->architecture); + g_free (self->os); + flatpak_oci_image_rootfs_destroy (&self->rootfs); + flatpak_oci_image_config_destroy (&self->config); + + for (i = 0; self->history != NULL && self->history[i] != NULL; i++) + flatpak_oci_image_history_free (self->history[i]); + g_free (self->history); + + G_OBJECT_CLASS (flatpak_oci_image_parent_class)->finalize (object); +} + +static void +flatpak_oci_image_class_init (FlatpakOciImageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakJsonClass *json_class = FLATPAK_JSON_CLASS (klass); + static FlatpakJsonProp config_props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciImageConfig, user, "User"), + FLATPAK_JSON_INT64_PROP (FlatpakOciImageConfig, memory, "Memory"), + FLATPAK_JSON_INT64_PROP (FlatpakOciImageConfig, memory_swap, "MemorySwap"), + FLATPAK_JSON_INT64_PROP (FlatpakOciImageConfig, cpu_shares, "CpuShares"), + FLATPAK_JSON_BOOLMAP_PROP (FlatpakOciImageConfig, exposed_ports, "ExposedPorts"), + FLATPAK_JSON_STRV_PROP (FlatpakOciImageConfig, env, "Env"), + FLATPAK_JSON_STRV_PROP (FlatpakOciImageConfig, entrypoint, "Entrypoint"), + FLATPAK_JSON_STRV_PROP (FlatpakOciImageConfig, cmd, "Cmd"), + FLATPAK_JSON_BOOLMAP_PROP (FlatpakOciImageConfig, volumes, "Volumes"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImageConfig, working_dir, "WorkingDir"), + FLATPAK_JSON_STRMAP_PROP(FlatpakOciImageConfig, labels, "Labels"), + FLATPAK_JSON_LAST_PROP + }; + static FlatpakJsonProp rootfs_props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciImageRootfs, type, "type"), + FLATPAK_JSON_STRV_PROP (FlatpakOciImageRootfs, diff_ids, "diff_ids"), + FLATPAK_JSON_LAST_PROP + }; + static FlatpakJsonProp history_props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciImageHistory, created, "created"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImageHistory, created_by, "created_by"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImageHistory, author, "author"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImageHistory, comment, "comment"), + FLATPAK_JSON_BOOL_PROP (FlatpakOciImageHistory, empty_layer, "empty_layer"), + FLATPAK_JSON_LAST_PROP + }; + static FlatpakJsonProp props[] = { + FLATPAK_JSON_STRING_PROP (FlatpakOciImage, created, "created"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImage, author, "author"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImage, architecture, "architecture"), + FLATPAK_JSON_STRING_PROP (FlatpakOciImage, os, "os"), + FLATPAK_JSON_STRUCT_PROP (FlatpakOciImage, config, "config", config_props), + FLATPAK_JSON_STRUCT_PROP (FlatpakOciImage, rootfs, "rootfs", rootfs_props), + FLATPAK_JSON_STRUCTV_PROP (FlatpakOciImage, history, "history", history_props), + FLATPAK_JSON_LAST_PROP + }; + + object_class->finalize = flatpak_oci_image_finalize; + json_class->props = props; + json_class->mediatype = FLATPAK_OCI_MEDIA_TYPE_IMAGE_CONFIG; +} + +static void +flatpak_oci_image_init (FlatpakOciImage *self) +{ +} + +FlatpakOciImage * +flatpak_oci_image_new (void) +{ + FlatpakOciImage *image; + GTimeVal stamp; + + stamp.tv_sec = time (NULL); + stamp.tv_usec = 0; + + image = g_object_new (FLATPAK_TYPE_OCI_IMAGE, NULL); + + /* Some default values */ + image->created = g_time_val_to_iso8601 (&stamp); + image->architecture = g_strdup ("arm64"); + image->os = g_strdup ("linux"); + + image->rootfs.type = g_strdup ("layers"); + image->rootfs.diff_ids = g_new0 (char *, 1); + + return image; +} + +void +flatpak_oci_image_set_created (FlatpakOciImage *image, + const char *created) +{ + g_free (image->created); + image->created = g_strdup (created); +} + +void +flatpak_oci_image_set_architecture (FlatpakOciImage *image, + const char *arch) +{ + g_free (image->architecture); + image->architecture = g_strdup (arch); +} + +void +flatpak_oci_image_set_os (FlatpakOciImage *image, + const char *os) +{ + g_free (image->os); + image->os = g_strdup (os); +} + +void +flatpak_oci_image_set_layers (FlatpakOciImage *image, + const char **layers) +{ + g_strfreev (image->rootfs.diff_ids); + image->rootfs.diff_ids = g_strdupv ((char **)layers); +} + +void +flatpak_oci_image_set_layer (FlatpakOciImage *image, + const char *layer) +{ + const char *layers[] = {layer, NULL}; + + flatpak_oci_image_set_layers (image, layers); +} + +static void +add_annotation (GHashTable *annotations, const char *key, const char *value) +{ + g_hash_table_replace (annotations, + g_strdup (key), + g_strdup (value)); +} + +void +flatpak_oci_add_annotations_for_commit (GHashTable *annotations, + const char *ref, + const char *commit, + GVariant *commit_data) +{ + if (ref) + add_annotation (annotations,"org.flatpak.Ostree.Ref", ref); + + if (commit) + add_annotation (annotations,"org.flatpak.Ostree.Commit", commit); + + if (commit_data) + { + g_autofree char *parent = NULL; + g_autofree char *subject = NULL; + g_autofree char *body = NULL; + g_autofree char *timestamp = NULL; + g_autoptr(GVariant) metadata = NULL; + int i; + + parent = ostree_commit_get_parent (commit_data); + if (parent) + add_annotation (annotations, "org.flatpak.Ostree.ParentCommit", parent); + + metadata = g_variant_get_child_value (commit_data, 0); + for (i = 0; i < g_variant_n_children (metadata); i++) + { + g_autoptr(GVariant) elm = g_variant_get_child_value (metadata, i); + g_autoptr(GVariant) value = g_variant_get_child_value (elm, 1); + g_autofree char *key = NULL; + g_autofree char *full_key = NULL; + g_autofree char *value_base64 = NULL; + + g_variant_get_child (elm, 0, "s", &key); + full_key = g_strdup_printf ("org.flatpak.Ostree.Metadata.%s", key); + + value_base64 = g_base64_encode (g_variant_get_data (value), g_variant_get_size (value)); + add_annotation (annotations, full_key, value_base64); + } + + timestamp = g_strdup_printf ("%"G_GUINT64_FORMAT, ostree_commit_get_timestamp (commit_data)); + add_annotation (annotations, "org.flatpak.Ostree.Timestamp", timestamp); + + g_variant_get_child (commit_data, 3, "s", &subject); + add_annotation (annotations, "org.flatpak.Ostree.Subject", subject); + + g_variant_get_child (commit_data, 4, "s", &body); + add_annotation (annotations, "org.flatpak.Ostree.Body", body); + } +} + +void +flatpak_oci_parse_commit_annotations (GHashTable *annotations, + guint64 *out_timestamp, + char **out_subject, + char **out_body, + char **out_ref, + char **out_commit, + char **out_parent_commit, + GVariantBuilder *metadata_builder) +{ + const char *oci_timestamp, *oci_subject, *oci_body, *oci_parent_commit, *oci_commit, *oci_ref; + GHashTableIter iter; + gpointer _key, _value; + + oci_ref = g_hash_table_lookup (annotations, "org.flatpak.Ostree.Ref"); + if (oci_ref != NULL && out_ref != NULL && *out_ref == NULL) + *out_ref = g_strdup (oci_ref); + + oci_commit = g_hash_table_lookup (annotations, "org.flatpak.Ostree.Commit"); + if (oci_commit != NULL && out_commit != NULL && *out_commit == NULL) + *out_commit = g_strdup (oci_commit); + + oci_parent_commit = g_hash_table_lookup (annotations, "org.flatpak.Ostree.ParentCommit"); + if (oci_parent_commit != NULL && out_parent_commit != NULL && *out_parent_commit == NULL) + *out_parent_commit = g_strdup (oci_parent_commit); + + oci_timestamp = g_hash_table_lookup (annotations, "org.flatpak.Ostree.Timestamp"); + if (oci_timestamp != NULL && out_timestamp != NULL && *out_timestamp == 0) + *out_timestamp = g_ascii_strtoull (oci_timestamp, NULL, 10); + + oci_subject = g_hash_table_lookup (annotations, "org.flatpak.Ostree.Subject"); + if (oci_subject != NULL && out_subject != NULL && *out_subject == NULL) + *out_subject = g_strdup (oci_subject); + + oci_body = g_hash_table_lookup (annotations, "org.flatpak.Ostree.Body"); + if (oci_body != NULL && out_body != NULL && *out_body == NULL) + *out_body = g_strdup (oci_body); + + if (metadata_builder) + { + g_hash_table_iter_init (&iter, annotations); + while (g_hash_table_iter_next (&iter, &_key, &_value)) + { + const char *key = _key; + const char *value = _value; + guchar *bin; + gsize bin_len; + g_autoptr(GVariant) data = NULL; + + if (!g_str_has_prefix (key, "org.flatpak.Ostree.Metadata.")) + continue; + key += strlen ("org.flatpak.Ostree.Metadata."); + + bin = g_base64_decode (value, &bin_len); + data = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE("v"), bin, bin_len, FALSE, + g_free, bin)); + g_variant_builder_add (metadata_builder, "{s@v}", key, data); + } + } +} diff --git a/common/flatpak-json-oci.h b/common/flatpak-json-oci.h new file mode 100644 index 000000000..9a63deb0f --- /dev/null +++ b/common/flatpak-json-oci.h @@ -0,0 +1,231 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * This program 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.1 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, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_JSON_OCI_H__ +#define __FLATPAK_JSON_OCI_H__ + +#include "flatpak-json.h" + +G_BEGIN_DECLS + +#define FLATPAK_OCI_MEDIA_TYPE_DESCRIPTOR "application/vnd.oci.descriptor.v1+json" +#define FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST "application/vnd.oci.image.manifest.v1+json" +#define FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFESTLIST "application/vnd.oci.image.manifest.list.v1+json" +#define FLATPAK_OCI_MEDIA_TYPE_IMAGE_LAYER "application/vnd.oci.image.layer.v1.tar+gzip" +#define FLATPAK_OCI_MEDIA_TYPE_IMAGE_LAYER_NONDISTRIBUTABLE "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" +#define FLATPAK_OCI_MEDIA_TYPE_IMAGE_CONFIG "application/vnd.oci.image.config.v1+json" + +const char * flatpak_arch_to_oci_arch (const char *flatpak_arch); + +typedef struct { + char *mediatype; + char *digest; + gint64 size; + char **urls; +} FlatpakOciDescriptor; + +void flatpak_oci_descriptor_destroy (FlatpakOciDescriptor *self); +void flatpak_oci_descriptor_free (FlatpakOciDescriptor *self); + +typedef struct +{ + char *architecture; + char *os; + char *os_version; + char **os_features; + char *variant; + char **features; +} FlatpakOciManifestPlatform; + +typedef struct +{ + FlatpakOciDescriptor parent; + FlatpakOciManifestPlatform platform; +} FlatpakOciManifestDescriptor; + +void flatpak_oci_manifest_descriptor_destroy (FlatpakOciManifestDescriptor *self); +void flatpak_oci_manifest_descriptor_free (FlatpakOciManifestDescriptor *self); + +#define FLATPAK_TYPE_OCI_REF flatpak_oci_ref_get_type () +G_DECLARE_FINAL_TYPE (FlatpakOciRef, flatpak_oci_ref, FLATPAK_OCI, REF, FlatpakJson) + +struct _FlatpakOciRef { + FlatpakJson parent; + + FlatpakOciDescriptor descriptor; +}; + +struct _FlatpakOciRefClass { + FlatpakJsonClass parent_class; +}; + +FlatpakOciRef *flatpak_oci_ref_new (const char *mediatype, + const char *digest, + gint64 size); +const char * flatpak_oci_ref_get_mediatype (FlatpakOciRef *self); +const char * flatpak_oci_ref_get_digest (FlatpakOciRef *self); +gint64 flatpak_oci_ref_get_size (FlatpakOciRef *self); +const char ** flatpak_oci_ref_get_urls (FlatpakOciRef *self); +void flatpak_oci_ref_set_urls (FlatpakOciRef *self, + const char **urls); + + +#define FLATPAK_TYPE_OCI_VERSIONED flatpak_oci_versioned_get_type () +G_DECLARE_FINAL_TYPE (FlatpakOciVersioned, flatpak_oci_versioned, FLATPAK_OCI, VERSIONED, FlatpakJson) + +struct _FlatpakOciVersioned { + FlatpakJson parent; + + int version; + char *mediatype; +}; + +struct _FlatpakOciVersionedClass { + FlatpakJsonClass parent_class; +}; + +FlatpakOciVersioned *flatpak_oci_versioned_from_json (GBytes *bytes, + GError **error); +const char * flatpak_oci_versioned_get_mediatype (FlatpakOciVersioned *self); +gint64 flatpak_oci_versioned_get_version (FlatpakOciVersioned *self); + +#define FLATPAK_TYPE_OCI_MANIFEST flatpak_oci_manifest_get_type () +G_DECLARE_FINAL_TYPE (FlatpakOciManifest, flatpak_oci_manifest, FLATPAK, OCI_MANIFEST, FlatpakOciVersioned) + +struct _FlatpakOciManifest +{ + FlatpakOciVersioned parent; + + FlatpakOciDescriptor config; + FlatpakOciDescriptor **layers; + GHashTable *annotations; +}; + +struct _FlatpakOciManifestClass +{ + FlatpakOciVersionedClass parent_class; +}; + + +FlatpakOciManifest *flatpak_oci_manifest_new (void); +void flatpak_oci_manifest_set_config (FlatpakOciManifest *self, + FlatpakOciRef *ref); +void flatpak_oci_manifest_set_layers (FlatpakOciManifest *self, + FlatpakOciRef **refs); +void flatpak_oci_manifest_set_layer (FlatpakOciManifest *self, + FlatpakOciRef *ref); +int flatpak_oci_manifest_get_n_layers (FlatpakOciManifest *self); +const char * flatpak_oci_manifest_get_layer_digest (FlatpakOciManifest *self, + int i); +GHashTable * flatpak_oci_manifest_get_annotations (FlatpakOciManifest *self); + +#define FLATPAK_TYPE_OCI_MANIFEST_LIST flatpak_oci_manifest_list_get_type () +G_DECLARE_FINAL_TYPE (FlatpakOciManifestList, flatpak_oci_manifest_list, FLATPAK, OCI_MANIFEST_LIST, FlatpakOciVersioned) + +struct _FlatpakOciManifestList +{ + FlatpakOciVersioned parent; + + FlatpakOciManifestDescriptor **manifests; + GHashTable *annotations; +}; + +struct _FlatpakOciManifestListClass +{ + FlatpakOciVersionedClass parent_class; +}; + +#define FLATPAK_TYPE_OCI_IMAGE flatpak_oci_image_get_type () +G_DECLARE_FINAL_TYPE (FlatpakOciImage, flatpak_oci_image, FLATPAK, OCI_IMAGE, FlatpakJson) + +typedef struct +{ + char *type; + char **diff_ids; +} FlatpakOciImageRootfs; + +typedef struct +{ + char *user; + char *working_dir; + gint64 memory; + gint64 memory_swap; + gint64 cpu_shares; + char **env; + char **cmd; + char **entrypoint; + char **exposed_ports; + char **volumes; + GHashTable *labels; +} FlatpakOciImageConfig; + +typedef struct +{ + char *created; + char *created_by; + char *author; + char *comment; + gboolean empty_layer; +} FlatpakOciImageHistory; + +struct _FlatpakOciImage +{ + FlatpakJson parent; + + char *created; + char *author; + char *architecture; + char *os; + FlatpakOciImageRootfs rootfs; + FlatpakOciImageConfig config; + FlatpakOciImageHistory **history; +}; + +struct _FlatpakOciImageClass +{ + FlatpakJsonClass parent_class; +}; + +FlatpakOciImage *flatpak_oci_image_new (void); +void flatpak_oci_image_set_created (FlatpakOciImage *image, + const char *created); +void flatpak_oci_image_set_architecture (FlatpakOciImage *image, + const char *arch); +void flatpak_oci_image_set_os (FlatpakOciImage *image, + const char *os); +void flatpak_oci_image_set_layers (FlatpakOciImage *image, + const char **layers); +void flatpak_oci_image_set_layer (FlatpakOciImage *image, + const char *layer); + +void flatpak_oci_add_annotations_for_commit (GHashTable *annotations, + const char *ref, + const char *commit, + GVariant *commit_data); +void flatpak_oci_parse_commit_annotations (GHashTable *annotations, + guint64 *out_timestamp, + char **out_subject, + char **out_body, + char **out_ref, + char **out_commit, + char **out_parent_commit, + GVariantBuilder *metadata_builder); + +#endif /* __FLATPAK_JSON_OCI_H__ */ diff --git a/common/flatpak-json.c b/common/flatpak-json.c new file mode 100644 index 000000000..b4c5cec93 --- /dev/null +++ b/common/flatpak-json.c @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2015 Red Hat, Inc + * + * This file 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 file 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 program. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" +#include "string.h" + +#include "flatpak-json.h" +#include "flatpak-utils.h" +#include "libglnx.h" + +G_DEFINE_TYPE (FlatpakJson, flatpak_json, G_TYPE_OBJECT); + +static void +flatpak_json_finalize (GObject *object) +{ + G_OBJECT_CLASS (flatpak_json_parent_class)->finalize (object); +} + +static void +flatpak_json_class_init (FlatpakJsonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = flatpak_json_finalize; +} + +static void +flatpak_json_init (FlatpakJson *self) +{ +} + +static gboolean +demarshal (JsonNode *parent_node, + const char *name, + gpointer dest, + FlatpakJsonPropType type, + gpointer type_data, + gpointer type_data2, + GError **error) +{ + JsonObject *parent_object; + JsonNode *node; + + if (type != FLATPAK_JSON_PROP_TYPE_PARENT) + { + parent_object = json_node_get_object (parent_node); + node = json_object_get_member (parent_object, name); + } + else + node = parent_node; + + if (node == NULL || JSON_NODE_TYPE (node) == JSON_NODE_NULL) + return TRUE; + + switch (type) + { + case FLATPAK_JSON_PROP_TYPE_STRING: + if (!JSON_NODE_HOLDS_VALUE (node) || + json_node_get_value_type (node) != G_TYPE_STRING) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting string for property %s", name); + return FALSE; + } + *(char **)dest = g_strdup (json_node_get_string (node)); + break; + + case FLATPAK_JSON_PROP_TYPE_INT64: + if (!JSON_NODE_HOLDS_VALUE (node) || + json_node_get_value_type (node) != G_TYPE_INT64) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting int64 for property %s", name); + return FALSE; + } + *(gint64 *)dest = json_node_get_int (node); + break; + + case FLATPAK_JSON_PROP_TYPE_BOOL: + if (!JSON_NODE_HOLDS_VALUE (node) || + json_node_get_value_type (node) != G_TYPE_BOOLEAN) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting bool for property %s", name); + return FALSE; + } + *(gboolean *)dest = json_node_get_boolean (node); + break; + + case FLATPAK_JSON_PROP_TYPE_STRV: + if (!JSON_NODE_HOLDS_ARRAY (node)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting array for property %s", name); + return FALSE; + } + { + JsonArray *array = json_node_get_array (node); + guint i, array_len = json_array_get_length (array); + g_autoptr(GPtrArray) str_array = g_ptr_array_sized_new (array_len + 1); + + for (i = 0; i < array_len; i++) + { + JsonNode *val = json_array_get_element (array, i); + + if (JSON_NODE_TYPE (val) != JSON_NODE_VALUE) + continue; + + if (json_node_get_string (val) != NULL) + g_ptr_array_add (str_array, (gpointer) g_strdup (json_node_get_string (val))); + } + + g_ptr_array_add (str_array, NULL); + *(char ***)dest = (char **)g_ptr_array_free (g_steal_pointer (&str_array), FALSE); + } + + break; + + case FLATPAK_JSON_PROP_TYPE_PARENT: + case FLATPAK_JSON_PROP_TYPE_STRUCT: + if (!JSON_NODE_HOLDS_OBJECT (node)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting object for property %s", name); + return FALSE; + } + { + FlatpakJsonProp *struct_props = type_data; + int i; + + for (i = 0; struct_props[i].name != NULL; i++) + { + if (!demarshal (node, struct_props[i].name, + G_STRUCT_MEMBER_P (dest, struct_props[i].offset), + struct_props[i].type, struct_props[i].type_data, struct_props[i].type_data2, + error)) + return FALSE; + } + } + break; + + case FLATPAK_JSON_PROP_TYPE_STRUCTV: + if (!JSON_NODE_HOLDS_ARRAY (node)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting array for property %s", name); + return FALSE; + } + { + JsonArray *array = json_node_get_array (node); + guint array_len = json_array_get_length (array); + FlatpakJsonProp *struct_props = type_data; + g_autoptr(GPtrArray) obj_array = g_ptr_array_sized_new (array_len + 1); + int i, j; + gboolean res = TRUE; + + for (j = 0; res && j < array_len; j++) + { + JsonNode *val = json_array_get_element (array, j); + gpointer new_element; + + if (!JSON_NODE_HOLDS_OBJECT (val)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting object elemen for property %s", name); + res = FALSE; + break; + } + + new_element = g_malloc0 ((gsize)type_data2); + g_ptr_array_add (obj_array, new_element); + + for (i = 0; struct_props[i].name != NULL; i++) + { + if (!demarshal (val, struct_props[i].name, + G_STRUCT_MEMBER_P (new_element, struct_props[i].offset), + struct_props[i].type, struct_props[i].type_data, struct_props[i].type_data2, + error)) + { + res = FALSE; + break; + } + } + + } + + /* NULL terminate */ + g_ptr_array_add (obj_array, NULL); + + /* We always set the array, even if it is partial, because we don't know how + to free what we demarshalled so far */ + *(gpointer *)dest = (gpointer *)g_ptr_array_free (g_steal_pointer (&obj_array), FALSE); + return res; + } + break; + + case FLATPAK_JSON_PROP_TYPE_STRMAP: + if (!JSON_NODE_HOLDS_OBJECT (node)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting object for property %s", name); + return FALSE; + } + { + g_autoptr(GHashTable) h = NULL; + JsonObject *object = json_node_get_object (node); + g_autoptr(GList) members = NULL; + GList *l; + + h = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + members = json_object_get_members (object); + for (l = members; l != NULL; l = l->next) + { + const char *member_name = l->data; + JsonNode *val; + const char *val_str; + + val = json_object_get_member (object, member_name); + val_str = json_node_get_string (val); + if (val_str == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Wrong type for string member %s", member_name); + return FALSE; + } + + g_hash_table_insert (h, g_strdup (member_name), g_strdup (val_str)); + } + + *(GHashTable **)dest = g_steal_pointer (&h); + } + break; + + case FLATPAK_JSON_PROP_TYPE_BOOLMAP: + if (!JSON_NODE_HOLDS_OBJECT (node)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting object for property %s", name); + return FALSE; + } + { + JsonObject *object = json_node_get_object (node); + g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free); + g_autoptr(GList) members = NULL; + GList *l; + + members = json_object_get_members (object); + for (l = members; l != NULL; l = l->next) + { + const char *member_name = l->data; + + g_ptr_array_add (res, g_strdup (member_name)); + } + + g_ptr_array_add (res, NULL); + + *(char ***)dest = (char **)g_ptr_array_free (g_steal_pointer (&res), FALSE); + } + break; + + default: + g_assert_not_reached (); + } + + return TRUE; +} + +FlatpakJson * +flatpak_json_from_node (JsonNode *node, GType type, GError **error) +{ + g_autoptr(FlatpakJson) json = NULL; + FlatpakJsonProp *props = NULL; + gpointer class; + int i; + + /* We should handle these before we get here */ + g_assert (node != NULL); + g_assert (JSON_NODE_TYPE (node) != JSON_NODE_NULL); + + if (JSON_NODE_TYPE (node) != JSON_NODE_OBJECT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expecting a JSON object, but the node is of type `%s'", + json_node_type_name (node)); + return NULL; + } + + json = g_object_new (type, NULL); + + class = FLATPAK_JSON_GET_CLASS (json); + while (FLATPAK_JSON_CLASS (class)->props != NULL) + { + props = FLATPAK_JSON_CLASS (class)->props; + for (i = 0; props[i].name != NULL; i++) + { + if (!demarshal (node, props[i].name, + G_STRUCT_MEMBER_P (json, props[i].offset), + props[i].type, props[i].type_data, props[i].type_data2, + error)) + return NULL; + } + class = g_type_class_peek_parent (class); + } + + return g_steal_pointer (&json); +} + +FlatpakJson * +flatpak_json_from_bytes (GBytes *bytes, + GType type, + GError **error) +{ + g_autoptr(JsonParser) parser = NULL; + JsonNode *root = NULL; + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + error)) + return NULL; + + root = json_parser_get_root (parser); + + return flatpak_json_from_node (root, type, error); +} + +static JsonNode * +marshal (JsonObject *parent, + const char *name, + gpointer src, + FlatpakJsonPropType type, + gpointer type_data) +{ + JsonNode *retval = NULL; + + switch (type) + { + case FLATPAK_JSON_PROP_TYPE_STRING: + { + const char *str = *(const char **)src; + if (str != NULL) + retval = json_node_init_string (json_node_alloc (), str); + break; + } + + case FLATPAK_JSON_PROP_TYPE_INT64: + { + retval = json_node_init_int (json_node_alloc (), *(gint64 *)src); + break; + } + + case FLATPAK_JSON_PROP_TYPE_BOOL: + { + gboolean val = *(gboolean *)src; + if (val) + retval = json_node_init_boolean (json_node_alloc (), val); + break; + } + + case FLATPAK_JSON_PROP_TYPE_STRV: + { + char **strv = *(char ***)src; + int i; + JsonArray *array; + + if (strv != NULL && strv[0] != NULL) + { + array = json_array_sized_new (g_strv_length (strv)); + for (i = 0; strv[i] != NULL; i++) + { + JsonNode *str = json_node_new (JSON_NODE_VALUE); + + json_node_set_string (str, strv[i]); + json_array_add_element (array, str); + } + + retval = json_node_init_array (json_node_alloc (), array); + json_array_unref (array); + } + break; + } + + case FLATPAK_JSON_PROP_TYPE_PARENT: + case FLATPAK_JSON_PROP_TYPE_STRUCT: + { + FlatpakJsonProp *struct_props = type_data; + JsonObject *obj; + int i; + + if (type == FLATPAK_JSON_PROP_TYPE_PARENT) + obj = parent; + else + obj = json_object_new (); + + for (i = 0; struct_props[i].name != NULL; i++) + { + JsonNode *val = marshal (obj, struct_props[i].name, + G_STRUCT_MEMBER_P (src, struct_props[i].offset), + struct_props[i].type, struct_props[i].type_data); + + if (val == NULL) + continue; + + json_object_set_member (obj, struct_props[i].name, val); + } + + if (type != FLATPAK_JSON_PROP_TYPE_PARENT) + { + retval = json_node_new (JSON_NODE_OBJECT); + json_node_take_object (retval, obj); + } + break; + } + + case FLATPAK_JSON_PROP_TYPE_STRUCTV: + { + FlatpakJsonProp *struct_props = type_data; + gpointer *structv = *(gpointer **)src; + int i, j; + JsonArray *array; + + if (structv != NULL && structv[0] != NULL) + { + array = json_array_new (); + for (j = 0; structv[j] != NULL; j++) + { + JsonObject *obj = json_object_new (); + JsonNode *node; + + for (i = 0; struct_props[i].name != NULL; i++) + { + JsonNode *val = marshal (obj, struct_props[i].name, + G_STRUCT_MEMBER_P (structv[j], struct_props[i].offset), + struct_props[i].type, struct_props[i].type_data); + + if (val == NULL) + continue; + + json_object_set_member (obj, struct_props[i].name, val); + } + + node = json_node_new (JSON_NODE_OBJECT); + json_node_take_object (node, obj); + json_array_add_element (array, node); + } + + retval = json_node_init_array (json_node_alloc (), array); + json_array_unref (array); + } + + break; + } + + case FLATPAK_JSON_PROP_TYPE_STRMAP: + { + GHashTable *map = *(GHashTable **)src; + + if (map != NULL && g_hash_table_size (map) > 0) + { + GHashTableIter iter; + gpointer _key, _value; + JsonObject *object; + + object = json_object_new (); + + g_hash_table_iter_init (&iter, map); + while (g_hash_table_iter_next (&iter, &_key, &_value)) + { + const char *key = _key; + const char *value = _value; + JsonNode *str = json_node_new (JSON_NODE_VALUE); + + json_node_set_string (str, value); + json_object_set_member (object, key, str); + } + + retval = json_node_init_object (json_node_alloc (), object); + json_object_unref (object); + } + break; + } + + case FLATPAK_JSON_PROP_TYPE_BOOLMAP: + { + char **map = *(char ***)src; + + if (map != NULL && map[0] != NULL) + { + JsonObject *object; + int i; + + object = json_object_new (); + + for (i = 0; map[i] != NULL; i++) + { + const char *element = map[i]; + JsonObject *empty_o = json_object_new (); + JsonNode *empty = json_node_init_object (json_node_alloc (), empty_o); + json_object_unref (empty_o); + + json_object_set_member (object, element, empty); + } + + retval = json_node_init_object (json_node_alloc (), object); + json_object_unref (object); + } + break; + } + + default: + g_assert_not_reached (); + } + + return retval; +} + +static void +marshal_props_for_class (FlatpakJson *self, + FlatpakJsonClass *class, + JsonObject *obj) +{ + FlatpakJsonProp *props = NULL; + int i; + gpointer parent_class; + + parent_class = g_type_class_peek_parent (class); + + if (FLATPAK_JSON_CLASS (parent_class)->props != NULL) + marshal_props_for_class (self, + FLATPAK_JSON_CLASS(parent_class), + obj); + + props = FLATPAK_JSON_CLASS (class)->props; + for (i = 0; props[i].name != NULL; i++) + { + JsonNode *val = marshal (obj, props[i].name, + G_STRUCT_MEMBER_P (self, props[i].offset), + props[i].type, props[i].type_data); + + if (val == NULL) + continue; + + json_object_set_member (obj, props[i].name, val); + } +} + +JsonNode * +flatpak_json_to_node (FlatpakJson *self) +{ + JsonNode *retval; + JsonObject *obj; + gpointer class; + + if (self == NULL) + json_node_new (JSON_NODE_NULL); + + obj = json_object_new (); + + class = FLATPAK_JSON_GET_CLASS (self); + marshal_props_for_class (self, FLATPAK_JSON_CLASS (class), obj); + + retval = json_node_new (JSON_NODE_OBJECT); + json_node_take_object (retval, obj); + + return retval; +} + +GBytes * +flatpak_json_to_bytes (FlatpakJson *self) +{ + g_autoptr(JsonNode) node = NULL; + char *str; + + node = flatpak_json_to_node (FLATPAK_JSON (self)); + + str = json_to_string (node, TRUE); + return g_bytes_new_take (str, strlen (str)); +} diff --git a/common/flatpak-json.h b/common/flatpak-json.h new file mode 100644 index 000000000..9425cbe51 --- /dev/null +++ b/common/flatpak-json.h @@ -0,0 +1,92 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * This program 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.1 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, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_JSON_H__ +#define __FLATPAK_JSON_H__ + +#include + +G_BEGIN_DECLS + +#define FLATPAK_TYPE_JSON flatpak_json_get_type () + +typedef struct _FlatpakJsonProp FlatpakJsonProp; + +typedef enum { + FLATPAK_JSON_PROP_TYPE_PARENT, + FLATPAK_JSON_PROP_TYPE_INT64, + FLATPAK_JSON_PROP_TYPE_BOOL, + FLATPAK_JSON_PROP_TYPE_STRING, + FLATPAK_JSON_PROP_TYPE_STRUCT, + FLATPAK_JSON_PROP_TYPE_STRUCTV, + FLATPAK_JSON_PROP_TYPE_STRV, + FLATPAK_JSON_PROP_TYPE_STRMAP, + FLATPAK_JSON_PROP_TYPE_BOOLMAP, +} FlatpakJsonPropType; + +struct _FlatpakJsonProp { + const char *name; + gsize offset; + FlatpakJsonPropType type; + gpointer type_data; + gpointer type_data2; +} ; + +#define FLATPAK_JSON_STRING_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_STRING } +#define FLATPAK_JSON_INT64_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_INT64 } +#define FLATPAK_JSON_BOOL_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_BOOL } +#define FLATPAK_JSON_STRV_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_STRV } +#define FLATPAK_JSON_STRMAP_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_STRMAP } +#define FLATPAK_JSON_BOOLMAP_PROP(_struct, _field, _name) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_BOOLMAP } +#define FLATPAK_JSON_STRUCT_PROP(_struct, _field, _name, _props) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_STRUCT, (gpointer)_props} +#define FLATPAK_JSON_PARENT_PROP(_struct, _field, _props) \ + { "parent", G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_PARENT, (gpointer)_props} +#define FLATPAK_JSON_STRUCTV_PROP(_struct, _field, _name, _props) \ + { _name, G_STRUCT_OFFSET (_struct, _field), FLATPAK_JSON_PROP_TYPE_STRUCTV, (gpointer)_props, (gpointer) sizeof (**((_struct *) 0)->_field) } +#define FLATPAK_JSON_LAST_PROP { NULL } + +G_DECLARE_DERIVABLE_TYPE (FlatpakJson, flatpak_json, FLATPAK, JSON, GObject) + +struct _FlatpakJsonClass { + GObjectClass parent_class; + + FlatpakJsonProp *props; + const char *mediatype; +}; + +FlatpakJson *flatpak_json_from_node (JsonNode *node, + GType type, + GError **error); +JsonNode *flatpak_json_to_node (FlatpakJson *self); +FlatpakJson *flatpak_json_from_bytes (GBytes *bytes, + GType type, + GError **error); +GBytes *flatpak_json_to_bytes (FlatpakJson *self); + +G_END_DECLS + +#endif /* __FLATPAK_JSON_H__ */ diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c new file mode 100644 index 000000000..b004fb270 --- /dev/null +++ b/common/flatpak-oci-registry.c @@ -0,0 +1,1095 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * This program 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.1 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, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include + +#include "libglnx.h" + +#include +#include "flatpak-oci-registry.h" +#include "flatpak-utils.h" + +#define MAX_JSON_SIZE (1024 * 1024) + +GLNX_DEFINE_CLEANUP_FUNCTION (void *, flatpak_local_free_write_archive, archive_write_free) +#define free_write_archive __attribute__((cleanup (flatpak_local_free_write_archive))) + +GLNX_DEFINE_CLEANUP_FUNCTION (void *, flatpak_local_free_read_archive, archive_read_free) +#define free_read_archive __attribute__((cleanup (flatpak_local_free_read_archive))) + +static void flatpak_oci_registry_initable_iface_init (GInitableIface *iface); + +struct FlatpakOciRegistry +{ + GObject parent; + + gboolean for_write; + gboolean valid; + char *uri; + int tmp_dfd; + + /* Local repos */ + int dfd; + + /* Remote repos */ + SoupSession *soup_session; + SoupURI *base_uri; +}; + +typedef struct +{ + GObjectClass parent_class; +} FlatpakOciRegistryClass; + +enum { + PROP_0, + + PROP_URI, + PROP_FOR_WRITE, + PROP_TMP_DFD, +}; + +G_DEFINE_TYPE_WITH_CODE (FlatpakOciRegistry, flatpak_oci_registry, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + flatpak_oci_registry_initable_iface_init)) + +static void +flatpak_oci_registry_finalize (GObject *object) +{ + FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); + + if (self->dfd != -1) + close (self->dfd); + + g_clear_object (&self->soup_session); + g_clear_pointer (&self->base_uri, soup_uri_free); + g_free (self->uri); + + G_OBJECT_CLASS (flatpak_oci_registry_parent_class)->finalize (object); +} + +static void +flatpak_oci_registry_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); + const char *uri; + + switch (prop_id) + { + case PROP_URI: + /* Ensure the base uri ends with a / so relative urls work */ + uri = g_value_get_string (value); + if (g_str_has_prefix (uri, "/")) + self->uri = g_strdup (uri); + else + self->uri = g_strconcat (uri, "/", NULL); + break; + case PROP_FOR_WRITE: + self->for_write = g_value_get_boolean (value); + break; + case PROP_TMP_DFD: + self->tmp_dfd = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +flatpak_oci_registry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); + + switch (prop_id) + { + case PROP_URI: + g_value_set_string (value, self->uri); + break; + case PROP_FOR_WRITE: + g_value_set_boolean (value, self->for_write); + break; + case PROP_TMP_DFD: + g_value_set_int (value, self->tmp_dfd); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +flatpak_oci_registry_class_init (FlatpakOciRegistryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = flatpak_oci_registry_finalize; + object_class->get_property = flatpak_oci_registry_get_property; + object_class->set_property = flatpak_oci_registry_set_property; + + g_object_class_install_property (object_class, + PROP_URI, + g_param_spec_string ("uri", + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_TMP_DFD, + g_param_spec_int ("tmp-dfd", + "", + "", + -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_FOR_WRITE, + g_param_spec_boolean ("for-write", + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +flatpak_oci_registry_init (FlatpakOciRegistry *self) +{ + self->dfd = -1; + self->tmp_dfd = -1; +} + +FlatpakOciRegistry * +flatpak_oci_registry_new (const char *uri, + gboolean for_write, + int tmp_dfd, + GCancellable *cancellable, + GError **error) +{ + FlatpakOciRegistry *oci_registry; + + oci_registry = g_initable_new (FLATPAK_TYPE_OCI_REGISTRY, + cancellable, error, + "uri", uri, + "for-write", for_write, + "tmp-dfd", tmp_dfd, + NULL); + + return oci_registry; +} + +static int +local_open_file (int dfd, + const char *subpath, + GCancellable *cancellable, + GError **error) +{ + glnx_fd_close int fd = -1; + + do + fd = openat (dfd, subpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); + while (G_UNLIKELY (fd == -1 && errno == EINTR)); + if (fd == -1) + { + glnx_set_error_from_errno (error); + return -1; + } + + return glnx_steal_fd (&fd); +} + +static GBytes * +local_load_file (int dfd, + const char *subpath, + GCancellable *cancellable, + GError **error) +{ + glnx_fd_close int fd = -1; + + fd = local_open_file (dfd, subpath, cancellable, error); + if (fd == -1) + return NULL; + + return glnx_fd_readall_bytes (fd, cancellable, error); +} + +static GBytes * +remote_load_file (SoupSession *soup_session, + SoupURI *base, + const char *subpath, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(SoupURI) uri = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autofree char *uri_s = NULL; + + uri = soup_uri_new_with_base (base, subpath); + if (uri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid relative url %s", subpath); + return NULL; + } + + uri_s = soup_uri_to_string (uri, FALSE); + bytes = flatpak_load_http_uri (soup_session, + uri_s, + NULL, NULL, + cancellable, error); + if (bytes == NULL) + return NULL; + + return g_steal_pointer (&bytes); +} + +static GBytes * +flatpak_oci_registry_load_file (FlatpakOciRegistry *self, + const char *subpath, + GCancellable *cancellable, + GError **error) +{ + if (self->dfd != -1) + return local_load_file (self->dfd, subpath, cancellable, error); + else + return remote_load_file (self->soup_session, self->base_uri, subpath, cancellable, error); +} + +static JsonNode * +parse_json (GBytes *bytes, GCancellable *cancellable, GError **error) +{ + g_autoptr(JsonParser) parser = NULL; + JsonNode *root = NULL; + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + error)) + return NULL; + + root = json_parser_get_root (parser); + if (root == NULL || !JSON_NODE_HOLDS_OBJECT (root)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid json, no root object"); + return NULL; + } + + return json_node_ref (root); +} + +static gboolean +verify_oci_version (GBytes *oci_layout_bytes, GCancellable *cancellable, GError **error) +{ + const char *version; + g_autoptr(JsonNode) node = NULL; + JsonObject *oci_layout; + + node = parse_json (oci_layout_bytes, cancellable, error); + if (node == NULL) + return FALSE; + + oci_layout = json_node_get_object (node); + + version = json_object_get_string_member (oci_layout, "imageLayoutVersion"); + if (version == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Unsupported oci repo: oci-layout version missing"); + return FALSE; + } + + if (strcmp (version, "1.0.0") != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unsupported existing oci-layout version %s (only 1.0.0 supported)", version); + return FALSE; + } + + return TRUE; +} + +static gboolean +flatpak_oci_registry_ensure_local (FlatpakOciRegistry *self, + gboolean for_write, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) dir = g_file_new_for_uri (self->uri); + glnx_fd_close int local_dfd = -1; + int dfd; + g_autoptr(GError) local_error = NULL; + g_autoptr(GBytes) oci_layout_bytes = NULL; + + if (self->dfd != -1) + dfd = self->dfd; + else + { + if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dir), + TRUE, &local_dfd, &local_error)) + { + if (for_write && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&local_error); + + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, flatpak_file_get_path_cached (dir), 0755, cancellable, error)) + return FALSE; + + if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dir), + TRUE, &local_dfd, error)) + return FALSE; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + + dfd = local_dfd; + } + + if (for_write) + { + if (!glnx_shutil_mkdir_p_at (dfd, "blobs/sha256", 0755, cancellable, error)) + return FALSE; + + if (!glnx_shutil_mkdir_p_at (dfd, "refs", 0755, cancellable, error)) + return FALSE; + } + + oci_layout_bytes = local_load_file (dfd, "oci-layout", cancellable, &local_error); + if (oci_layout_bytes == NULL) + { + if (for_write && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + const char *new_layout_data = "{\"imageLayoutVersion\": \"1.0.0\"}"; + + g_clear_error (&local_error); + + if (!glnx_file_replace_contents_at (dfd, "oci-layout", + (const guchar *)new_layout_data, + strlen (new_layout_data), + 0, + cancellable, error)) + return FALSE; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + else if (!verify_oci_version (oci_layout_bytes, cancellable, error)) + return FALSE; + + if (self->dfd == -1 && local_dfd != -1) + self->dfd = glnx_steal_fd (&local_dfd); + + return TRUE; +} + +static gboolean +flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, + gboolean for_write, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(SoupURI) baseuri = NULL; + g_autoptr(GBytes) oci_layout_bytes = NULL; + + if (for_write) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Writes are not supported for remote OCI registries"); + return FALSE; + } + + self->soup_session = flatpak_create_soup_session ("flatpak"); + baseuri = soup_uri_new (self->uri); + if (baseuri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid url %s", self->uri); + return FALSE; + } + + oci_layout_bytes = remote_load_file (self->soup_session, baseuri, "oci-layout", cancellable, error); + if (oci_layout_bytes == NULL) + return FALSE; + + if (!verify_oci_version (oci_layout_bytes, cancellable, error)) + return FALSE; + + self->base_uri = g_steal_pointer (&baseuri); + + return TRUE; +} + +static gboolean +flatpak_oci_registry_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (initable); + gboolean res; + + if (self->tmp_dfd == -1 && + !glnx_opendirat (AT_FDCWD, "/tmp", TRUE, &self->tmp_dfd, error)) + return FALSE; + + if (g_str_has_prefix (self->uri, "file:/")) + res = flatpak_oci_registry_ensure_local (self, self->for_write, cancellable, error); + else + res = flatpak_oci_registry_ensure_remote (self, self->for_write, cancellable, error); + + if (!res) + return FALSE; + + self->valid = TRUE; + + return TRUE; +} + +static void +flatpak_oci_registry_initable_iface_init (GInitableIface *iface) +{ + iface->init = flatpak_oci_registry_initable_init; +} + + +FlatpakOciRef * +flatpak_oci_registry_load_ref (FlatpakOciRegistry *self, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) bytes = NULL; + g_autofree char *subpath = g_strdup_printf ("refs/%s", ref); + g_autoptr(GError) local_error = NULL; + + g_assert (self->valid); + + bytes = flatpak_oci_registry_load_file (self, subpath, cancellable, &local_error); + if (bytes == NULL) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No tag '%s' found", ref); + else + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + + return (FlatpakOciRef *)flatpak_json_from_bytes (bytes, FLATPAK_TYPE_OCI_REF, error); +} + +gboolean +flatpak_oci_registry_set_ref (FlatpakOciRegistry *self, + const char *ref, + FlatpakOciRef *data, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) bytes = NULL; + g_autofree char *subpath = g_strdup_printf ("refs/%s", ref); + + g_assert (self->valid); + + bytes = flatpak_json_to_bytes (FLATPAK_JSON (data)); + + if (!glnx_file_replace_contents_at (self->dfd, subpath, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + 0, cancellable, error)) + return FALSE; + + return TRUE; +} + +static gboolean +write_update_checksum (GOutputStream *out, + gconstpointer data, + gsize len, + gsize *out_bytes_written, + GChecksum *checksum, + GCancellable *cancellable, + GError **error) +{ + if (out) + { + if (!g_output_stream_write_all (out, data, len, out_bytes_written, + cancellable, error)) + return FALSE; + } + else if (out_bytes_written) + { + *out_bytes_written = len; + } + + if (checksum) + g_checksum_update (checksum, data, len); + + return TRUE; +} + +static gboolean +splice_update_checksum (GOutputStream *out, + GInputStream *in, + GChecksum *checksum, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (out != NULL || checksum != NULL, FALSE); + + if (checksum != NULL) + { + gsize bytes_read, bytes_written; + char buf[4096]; + do + { + if (!g_input_stream_read_all (in, buf, sizeof(buf), &bytes_read, cancellable, error)) + return FALSE; + if (!write_update_checksum (out, buf, bytes_read, &bytes_written, checksum, + cancellable, error)) + return FALSE; + } + while (bytes_read > 0); + } + else if (out != NULL) + { + if (g_output_stream_splice (out, in, 0, cancellable, error) < 0) + return FALSE; + } + + return TRUE; +} + + +static char * +checksum_fd (int fd, GCancellable *cancellable, GError **error) +{ + g_autoptr(GChecksum) checksum = NULL; + g_autoptr(GInputStream) in = g_unix_input_stream_new (fd, FALSE); + + checksum = g_checksum_new (G_CHECKSUM_SHA256); + + if (!splice_update_checksum (NULL, in, checksum, cancellable, error)) + return NULL; + + return g_strdup (g_checksum_get_string (checksum)); +} + +int +flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, + const char *digest, + FlatpakLoadUriProgress progress_cb, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GTask) task = NULL; + g_autofree char *subpath = NULL; + g_autoptr(SoupURI) uri = NULL; + g_autoptr(GPtrArray) mirrorlist = g_ptr_array_new (); + g_autoptr(SoupRequest) req = NULL; + g_autoptr(GInputStream) input = NULL; + g_autoptr(GOutputStream) out = NULL; + glnx_fd_close int fd = -1; + + g_assert (self->valid); + + if (!g_str_has_prefix (digest, "sha256:")) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unsupported digest type %s", digest); + return -1; + } + + subpath = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:")); + + if (self->dfd != -1) + { + /* Local case, trust checksum */ + fd = local_open_file (self->dfd, subpath, cancellable, error); + if (fd == -1) + return -1; + } + else + { + g_autofree char *tmpfile_path = NULL; + g_autoptr(SoupURI) uri = NULL; + g_autofree char *uri_s = NULL; + g_autofree char *checksum = NULL; + g_autofree char *tmpfile_name = g_strdup_printf ("oci-layer-XXXXXX"); + g_autoptr(GOutputStream) out_stream = NULL; + + /* remote case, download and verify */ + + uri = soup_uri_new_with_base (self->base_uri, subpath); + if (uri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid relative url %s", subpath); + return -1; + } + + uri_s = soup_uri_to_string (uri, FALSE); + + if (!flatpak_open_in_tmpdir_at (self->tmp_dfd, 0600, tmpfile_name, + &out_stream, cancellable, error)) + return -1; + + fd = local_open_file (self->tmp_dfd, tmpfile_name, cancellable, error); + (void)unlinkat (self->tmp_dfd, tmpfile_name, 0); + + if (fd == -1) + return -1; + + if (!flatpak_download_http_uri (self->soup_session, uri_s, out_stream, + progress_cb, user_data, + cancellable, error)) + return -1; + + if (!g_output_stream_close (out_stream, cancellable, error)) + return -1; + + checksum = checksum_fd (fd, cancellable, error); + if (checksum == NULL) + return -1; + + if (strcmp (checksum, digest + strlen ("sha256:")) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Checksum digest did not match (%s != %s)", digest, checksum); + return -1; + } + + lseek (fd, 0, SEEK_SET); + } + + return glnx_steal_fd (&fd); +} + +GBytes * +flatpak_oci_registry_load_blob (FlatpakOciRegistry *self, + const char *digest, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *subpath = NULL; + + g_assert (self->valid); + + if (!g_str_has_prefix (digest, "sha256:")) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unsupported digest type %s", digest); + return NULL; + } + + subpath = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:")); + + return flatpak_oci_registry_load_file (self, subpath, cancellable, error); +} + +char * +flatpak_oci_registry_store_blob (FlatpakOciRegistry *self, + GBytes *data, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, data); + g_autofree char *subpath = NULL; + + g_assert (self->valid); + + subpath = g_strdup_printf ("blobs/sha256/%s", sha256); + if (!glnx_file_replace_contents_at (self->dfd, subpath, + g_bytes_get_data (data, NULL), + g_bytes_get_size (data), + 0, cancellable, error)) + return FALSE; + + return g_strdup_printf ("sha256:%s", sha256); +} + +FlatpakOciRef * +flatpak_oci_registry_store_json (FlatpakOciRegistry *self, + FlatpakJson *json, + GCancellable *cancellable, + GError **error) +{ + GBytes *bytes = flatpak_json_to_bytes (json); + g_autofree char *digest = NULL; + + digest = flatpak_oci_registry_store_blob (self, bytes, cancellable, error); + if (digest == NULL) + return NULL; + + return flatpak_oci_ref_new (FLATPAK_JSON_CLASS (FLATPAK_JSON_GET_CLASS (json))->mediatype, digest, g_bytes_get_size (bytes)); +} + +FlatpakOciVersioned * +flatpak_oci_registry_load_versioned (FlatpakOciRegistry *self, + const char *digest, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) bytes = NULL; + + g_assert (self->valid); + + bytes = flatpak_oci_registry_load_blob (self, digest, cancellable, error); + if (bytes == NULL) + return NULL; + + return flatpak_oci_versioned_from_json (bytes, error); +} + +FlatpakOciManifest * +flatpak_oci_registry_chose_image (FlatpakOciRegistry *self, + const char *tag, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FlatpakOciVersioned) versioned = NULL; + g_autoptr(FlatpakOciRef) ref = NULL; + + ref = flatpak_oci_registry_load_ref (self, tag, + cancellable, error); + if (ref == NULL) + return NULL; + + versioned = flatpak_oci_registry_load_versioned (self, + flatpak_oci_ref_get_digest (ref), + cancellable, error); + if (versioned == NULL) + return NULL; + + if (FLATPAK_IS_OCI_MANIFEST_LIST (versioned)) + { + /* TODO: Handle and set manifest */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "OCI manifest lists are not supported"); + return NULL; + } + + return g_steal_pointer (&versioned); +} + + +struct FlatpakOciLayerWriter +{ + GObject parent; + + FlatpakOciRegistry *registry; + + GChecksum *uncompressed_checksum; + GChecksum *compressed_checksum; + struct archive *archive; + GZlibCompressor *compressor; + guint64 uncompressed_size; + guint64 compressed_size; + char *tmp_path; + int tmp_fd; +}; + +typedef struct +{ + GObjectClass parent_class; +} FlatpakOciLayerWriterClass; + +G_DEFINE_TYPE (FlatpakOciLayerWriter, flatpak_oci_layer_writer, G_TYPE_OBJECT) + +static gboolean +propagate_libarchive_error (GError **error, + struct archive *a) +{ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s", archive_error_string (a)); + return FALSE; +} + +static void +flatpak_oci_layer_writer_reset (FlatpakOciLayerWriter *self) +{ + if (self->tmp_path) + { + (void) unlinkat (self->registry->dfd, self->tmp_path, 0); + g_free (self->tmp_path); + self->tmp_path = NULL; + } + + if (self->tmp_fd != -1) + { + close (self->tmp_fd); + self->tmp_fd = -1; + } + + g_clear_object (&self->compressor); + + g_checksum_reset (self->uncompressed_checksum); + g_checksum_reset (self->compressed_checksum); + + if (self->archive) + { + archive_write_free (self->archive); + self->archive = NULL; + } +} + + +static void +flatpak_oci_layer_writer_finalize (GObject *object) +{ + FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (object); + + flatpak_oci_layer_writer_reset (self); + + g_checksum_free (self->compressed_checksum); + g_checksum_free (self->uncompressed_checksum); + + g_clear_object (&self->registry); + + G_OBJECT_CLASS (flatpak_oci_layer_writer_parent_class)->finalize (object); +} + +static void +flatpak_oci_layer_writer_class_init (FlatpakOciLayerWriterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = flatpak_oci_layer_writer_finalize; + +} + +static void +flatpak_oci_layer_writer_init (FlatpakOciLayerWriter *self) +{ + self->uncompressed_checksum = g_checksum_new (G_CHECKSUM_SHA256); + self->compressed_checksum = g_checksum_new (G_CHECKSUM_SHA256); +} + +static int +flatpak_oci_layer_writer_open_cb (struct archive *archive, + void *client_data) +{ + return ARCHIVE_OK; +} + +static gssize +flatpak_oci_layer_writer_compress (FlatpakOciLayerWriter *self, + const void *buffer, + size_t length, + gboolean at_end) +{ + guchar compressed_buffer[8192]; + GConverterResult res; + gsize total_bytes_read, bytes_read, bytes_written, to_write_len; + guchar *to_write; + g_autoptr(GError) local_error = NULL; + GConverterFlags flags = 0; + bytes_read = 0; + + total_bytes_read = 0; + + if (at_end) + flags |= G_CONVERTER_INPUT_AT_END; + + do + { + res = g_converter_convert (G_CONVERTER (self->compressor), + buffer, length, + compressed_buffer, sizeof (compressed_buffer), + flags, &bytes_read, &bytes_written, + &local_error); + if (res == G_CONVERTER_ERROR) + { + archive_set_error (self->archive, EIO, "%s", local_error->message); + return -1; + } + + g_checksum_update (self->uncompressed_checksum, buffer, bytes_read); + g_checksum_update (self->compressed_checksum, compressed_buffer, bytes_written); + self->uncompressed_size += bytes_read; + self->compressed_size += bytes_written; + + to_write_len = bytes_written; + to_write = compressed_buffer; + while (to_write_len > 0) + { + ssize_t res = write (self->tmp_fd, to_write, to_write_len); + if (res <= 0) + { + if (errno == EINTR) + continue; + archive_set_error (self->archive, errno, "Write error"); + return -1; + } + + to_write_len -= res; + to_write += res; + } + + total_bytes_read += bytes_read; + } + while ((length > 0 && bytes_read == 0) || /* Repeat if we consumed nothing */ + (at_end && res != G_CONVERTER_FINISHED)); /* Or until finished if at_end */ + + return total_bytes_read; +} + +static ssize_t +flatpak_oci_layer_writer_write_cb (struct archive *archive, + void *client_data, + const void *buffer, + size_t length) +{ + FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (client_data); + + return flatpak_oci_layer_writer_compress (self, buffer, length, FALSE); +} + +static int +flatpak_oci_layer_writer_close_cb (struct archive *archive, + void *client_data) +{ + FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (client_data); + gssize res; + char buffer[1] = {0}; + + res = flatpak_oci_layer_writer_compress (self, &buffer, 0, TRUE); + if (res < 0) + return ARCHIVE_FATAL; + + return ARCHIVE_OK; +} + +FlatpakOciLayerWriter * +flatpak_oci_registry_write_layer (FlatpakOciRegistry *self, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FlatpakOciLayerWriter) oci_layer_writer = NULL; + free_write_archive struct archive *a = NULL; + glnx_fd_close int tmp_fd = -1; + g_autofree char *tmp_path = NULL; + + g_assert (self->valid); + + if (!self->for_write) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Write not supported to registry"); + return NULL; + } + + oci_layer_writer = g_object_new (FLATPAK_TYPE_OCI_LAYER_WRITER, NULL); + oci_layer_writer->registry = g_object_ref (self); + + if (!glnx_open_tmpfile_linkable_at (self->dfd, + "blobs/sha256", + O_WRONLY, + &tmp_fd, + &tmp_path, + error)) + return NULL; + + a = archive_write_new (); + if (archive_write_set_format_gnutar (a) != ARCHIVE_OK || + archive_write_add_filter_none (a) != ARCHIVE_OK) + { + propagate_libarchive_error (error, a); + return NULL; + } + + if (archive_write_open (a, oci_layer_writer, + flatpak_oci_layer_writer_open_cb, + flatpak_oci_layer_writer_write_cb, + flatpak_oci_layer_writer_close_cb) != ARCHIVE_OK) + { + propagate_libarchive_error (error, a); + return NULL; + } + + flatpak_oci_layer_writer_reset (oci_layer_writer); + + oci_layer_writer->archive = g_steal_pointer (&a); + oci_layer_writer->tmp_fd = glnx_steal_fd (&tmp_fd); + oci_layer_writer->tmp_path = g_steal_pointer (&tmp_path); + oci_layer_writer->compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); + + return g_steal_pointer (&oci_layer_writer); +} + +gboolean +flatpak_oci_layer_writer_close (FlatpakOciLayerWriter *self, + char **uncompressed_digest_out, + FlatpakOciRef **ref_out, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *path = NULL; + + if (archive_write_close (self->archive) != ARCHIVE_OK) + return propagate_libarchive_error (error, self->archive); + + path = g_strdup_printf ("blobs/sha256/%s", + g_checksum_get_string (self->compressed_checksum)); + + if (!glnx_link_tmpfile_at (self->registry->dfd, + GLNX_LINK_TMPFILE_REPLACE, + self->tmp_fd, + self->tmp_path, + self->registry->dfd, + path, + error)) + return FALSE; + + close (self->tmp_fd); + self->tmp_fd = -1; + g_free (self->tmp_path); + self->tmp_path = NULL; + + if (uncompressed_digest_out != NULL) + *uncompressed_digest_out = g_strdup_printf ("sha256:%s", g_checksum_get_string (self->uncompressed_checksum)); + if (ref_out != NULL) + { + g_autofree char *digest = g_strdup_printf ("sha256:%s", g_checksum_get_string (self->compressed_checksum)); + + *ref_out = flatpak_oci_ref_new (FLATPAK_OCI_MEDIA_TYPE_IMAGE_LAYER, digest, self->compressed_size); + } + + return TRUE; +} + +struct archive * +flatpak_oci_layer_writer_get_archive (FlatpakOciLayerWriter *self) +{ + return self->archive; +} diff --git a/common/flatpak-oci-registry.h b/common/flatpak-oci-registry.h new file mode 100644 index 000000000..1913d3d15 --- /dev/null +++ b/common/flatpak-oci-registry.h @@ -0,0 +1,104 @@ +/* + * Copyright © 2016 Red Hat, Inc + * + * This program 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.1 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, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_OCI_REGISTRY_H__ +#define __FLATPAK_OCI_REGISTRY_H__ + +#include "libglnx/libglnx.h" + +#include +#include +#include +#include "flatpak-json-oci.h" +#include "flatpak-utils.h" + +#define FLATPAK_TYPE_OCI_REGISTRY flatpak_oci_registry_get_type () +#define FLATPAK_OCI_REGISTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_OCI_REGISTRY, FlatpakOciRegistry)) +#define FLATPAK_IS_OCI_REGISTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_OCI_REGISTRY)) + + +GType flatpak_oci_registry_get_type (void); + +typedef struct FlatpakOciRegistry FlatpakOciRegistry; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakOciRegistry, g_object_unref) + +#define FLATPAK_TYPE_OCI_LAYER_WRITER flatpak_oci_layer_writer_get_type () +#define FLATPAK_OCI_LAYER_WRITER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_OCI_LAYER_WRITER, FlatpakOciLayerWriter)) +#define FLATPAK_IS_OCI_LAYER_WRITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_OCI_LAYER_WRITER)) + +GType flatpak_oci_layer_writer_get_type (void); + +typedef struct FlatpakOciLayerWriter FlatpakOciLayerWriter; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakOciLayerWriter, g_object_unref) + +FlatpakOciRegistry * flatpak_oci_registry_new (const char *uri, + gboolean for_write, + int tmp_dfd, + GCancellable *cancellable, + GError **error); +FlatpakOciRef * flatpak_oci_registry_load_ref (FlatpakOciRegistry *self, + const char *ref, + GCancellable *cancellable, + GError **error); +gboolean flatpak_oci_registry_set_ref (FlatpakOciRegistry *self, + const char *ref, + FlatpakOciRef *data, + GCancellable *cancellable, + GError **error); +int flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, + const char *digest, + FlatpakLoadUriProgress progress_cb, + gpointer user_data, + GCancellable *cancellable, + GError **error); +GBytes * flatpak_oci_registry_load_blob (FlatpakOciRegistry *self, + const char *digest, + GCancellable *cancellable, + GError **error); +char * flatpak_oci_registry_store_blob (FlatpakOciRegistry *self, + GBytes *data, + GCancellable *cancellable, + GError **error); +FlatpakOciRef * flatpak_oci_registry_store_json (FlatpakOciRegistry *self, + FlatpakJson *json, + GCancellable *cancellable, + GError **error); +FlatpakOciVersioned * flatpak_oci_registry_load_versioned (FlatpakOciRegistry *self, + const char *digest, + GCancellable *cancellable, + GError **error); +FlatpakOciLayerWriter *flatpak_oci_registry_write_layer (FlatpakOciRegistry *self, + GCancellable *cancellable, + GError **error); +FlatpakOciManifest *flatpak_oci_registry_chose_image (FlatpakOciRegistry *self, + const char *tag, + GCancellable *cancellable, + GError **error); + +struct archive *flatpak_oci_layer_writer_get_archive (FlatpakOciLayerWriter *self); +gboolean flatpak_oci_layer_writer_close (FlatpakOciLayerWriter *self, + char **uncompressed_digest_out, + FlatpakOciRef **ref_out, + GCancellable *cancellable, + GError **error); + +#endif /* __FLATPAK_OCI_REGISTRY_H__ */ diff --git a/lib/Makefile.am.inc b/lib/Makefile.am.inc index 9e3f81d1a..e1db0cf16 100644 --- a/lib/Makefile.am.inc +++ b/lib/Makefile.am.inc @@ -89,6 +89,7 @@ libflatpak_la_LIBADD = \ $(BASE_LIBS) \ $(OSTREE_LIBS) \ $(SOUP_LIBS) \ + $(JSON_LIBS) \ $(NULL) test_libflatpak_SOURCES = \