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 = \