diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc
index 0d75ba16..020452b3 100644
--- a/app/Makefile.am.inc
+++ b/app/Makefile.am.inc
@@ -7,8 +7,6 @@ flatpak_SOURCES = \
app/flatpak-builtins.h \
app/flatpak-builtins-utils.h \
app/flatpak-builtins-utils.c \
- app/flatpak-oci.h \
- app/flatpak-oci.c \
app/flatpak-transaction.h \
app/flatpak-transaction.c \
app/flatpak-builtins-add-remote.c \
diff --git a/app/flatpak-builtins-build-bundle.c b/app/flatpak-builtins-build-bundle.c
index b04af8fb..d70743e8 100644
--- a/app/flatpak-builtins-build-bundle.c
+++ b/app/flatpak-builtins-build-bundle.c
@@ -35,7 +35,7 @@
#include "flatpak-builtins.h"
#include "flatpak-utils.h"
-#include "flatpak-oci.h"
+#include "flatpak-oci-registry.h"
#include "flatpak-chain-input-stream.h"
#include "flatpak-builtins-utils.h"
@@ -268,102 +268,6 @@ timestamp_to_iso8601 (guint64 timestamp)
return g_time_val_to_iso8601 (&stamp);
}
-static GBytes *
-generate_config_json (guint64 timestamp,
- const char *layer_sha256,
- const char *arch)
-{
- g_autoptr(FlatpakJsonWriter) writer = flatpak_json_writer_new ();
- g_autofree char *created = timestamp_to_iso8601 (timestamp);
- g_autofree char *layer_digest = g_strdup_printf ("sha256:%s", layer_sha256);
-
- flatpak_json_writer_add_string_property (writer, "created", created);
- flatpak_json_writer_add_string_property (writer, "architecture", flatpak_arch_to_oci_arch (arch));
- flatpak_json_writer_add_string_property (writer, "os", "linux");
-
- flatpak_json_writer_add_struct_property (writer, "rootfs");
- {
- flatpak_json_writer_add_array_property (writer, "diff_ids");
- {
- flatpak_json_writer_add_array_string (writer, layer_digest);
- flatpak_json_writer_close (writer);
- }
- flatpak_json_writer_add_string_property (writer, "type", "layers");
- flatpak_json_writer_close (writer);
- }
-
- return flatpak_json_writer_get_result (writer);
-}
-
-static GBytes *
-generate_manifest_json (guint64 config_size,
- const char *config_sha256,
- guint64 layer_size,
- const char *layer_sha256,
- const char *ref,
- const char *checksum,
- GVariant *commit)
-{
- g_autoptr(FlatpakJsonWriter) writer = flatpak_json_writer_new ();
- g_autofree char *config_digest = g_strdup_printf ("sha256:%s", config_sha256);
- g_autofree char *layer_digest = g_strdup_printf ("sha256:%s", layer_sha256);
-
- flatpak_json_writer_add_uint64_property (writer, "schemaVersion", 2);
- flatpak_json_writer_add_string_property (writer, "mediaType", "application/vnd.oci.image.manifest.v1+json");
- flatpak_json_writer_add_struct_property (writer, "config");
- {
- flatpak_json_writer_add_string_property (writer, "mediaType", "application/vnd.oci.image.config.v1+json");
- flatpak_json_writer_add_uint64_property (writer, "size", config_size);
- flatpak_json_writer_add_string_property (writer, "digest", config_digest);
- flatpak_json_writer_close (writer);
- }
-
- flatpak_json_writer_add_array_property (writer, "layers");
- {
- flatpak_json_writer_add_array_struct (writer);
- {
- flatpak_json_writer_add_string_property (writer, "mediaType", "application/vnd.oci.image.layer.v1.tar+gzip");
- flatpak_json_writer_add_uint64_property (writer, "size", layer_size);
- flatpak_json_writer_add_string_property (writer, "digest", layer_digest);
- flatpak_json_writer_close (writer);
- }
- flatpak_json_writer_close (writer);
- }
-
- flatpak_json_writer_add_struct_property (writer, "annotations");
- {
- g_autofree char *parent = NULL;
- g_autofree char *subject = NULL;
- g_autofree char *body = NULL;
- g_autoptr(GVariant) metadata = NULL;
- g_autofree char *metadata_base64 = NULL;
-
- flatpak_json_writer_add_string_property (writer, "org.flatpak.Ref", ref);
-
- parent = ostree_commit_get_parent (commit);
- flatpak_json_writer_add_string_property (writer, "org.flatpak.ParentCommit", parent);
-
- flatpak_json_writer_add_string_property (writer, "org.flatpak.Commit", checksum);
-
- metadata = g_variant_get_child_value (commit, 0);
- if (g_variant_get_size (metadata) > 0)
- {
- metadata_base64 = g_base64_encode (g_variant_get_data (metadata), g_variant_get_size (metadata));
- flatpak_json_writer_add_string_property (writer, "org.flatpak.Metadata", metadata_base64);
- }
-
- g_variant_get_child (commit, 3, "s", &subject);
- flatpak_json_writer_add_string_property (writer, "org.flatpak.Subject", subject);
-
- g_variant_get_child (commit, 4, "s", &body);
- flatpak_json_writer_add_string_property (writer, "org.flatpak.Body", body);
-
- flatpak_json_writer_close (writer);
- }
-
- return flatpak_json_writer_get_result (writer);
-}
-
static gboolean
export_commit_to_archive (OstreeRepo *repo,
GFile *root,
@@ -388,24 +292,25 @@ build_oci (OstreeRepo *repo, GFile *dir,
GCancellable *cancellable, GError **error)
{
g_autoptr(GFile) root = NULL;
- g_autoptr(GFile) refs_tag = NULL;
- g_autoptr(GFile) refs = NULL;
g_autoptr(GVariant) commit_data = NULL;
g_autoptr(GVariant) commit_metadata = NULL;
g_autofree char *commit_checksum = NULL;
- g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1);
- glnx_fd_close int dfd = -1;
- g_autofree char *layer_compressed_sha256 = NULL;
- g_autofree char *layer_uncompressed_sha256 = NULL;
- guint64 layer_compressed_size;
- guint64 layer_uncompressed_size;
- g_autoptr(GBytes) config = NULL;
- g_autofree char *config_sha256 = NULL;
- g_autoptr(GBytes) manifest = NULL;
- g_autoptr(FlatpakOciDir) oci_dir = NULL;
+ g_autofree char *dir_uri = NULL;
+ g_autoptr(FlatpakOciRegistry) registry = NULL;
g_autoptr(FlatpakOciLayerWriter) layer_writer = NULL;
- g_autofree char *manifest_sha256 = NULL;
struct archive *archive;
+ g_autofree char *uncompressed_digest = NULL;
+ g_autofree char *timestamp = NULL;
+ g_autoptr(FlatpakOciImage) image = NULL;
+ g_autoptr(FlatpakOciRef) layer_ref = NULL;
+ g_autoptr(FlatpakOciRef) image_ref = NULL;
+ g_autoptr(FlatpakOciRef) manifest_ref = NULL;
+ g_autoptr(FlatpakOciManifest) manifest = NULL;
+ g_autoptr(GFile) metadata_file = NULL;
+ guint64 installed_size = 0;
+ GHashTable *annotations;
+ gsize metadata_size;
+ g_autofree char *metadata_contents = NULL;
if (!ostree_repo_resolve_rev (repo, ref, FALSE, &commit_checksum, error))
return FALSE;
@@ -419,50 +324,69 @@ build_oci (OstreeRepo *repo, GFile *dir,
if (!ostree_repo_read_commit_detached_metadata (repo, commit_checksum, &commit_metadata, cancellable, error))
return FALSE;
- oci_dir = flatpak_oci_dir_new ();
-
- if (!flatpak_oci_dir_ensure (oci_dir, dir, cancellable, error))
+ dir_uri = g_file_get_uri (dir);
+ registry = flatpak_oci_registry_new (dir_uri, TRUE, -1, cancellable, error);
+ if (registry == NULL)
return FALSE;
- layer_writer = flatpak_oci_layer_writer_new (oci_dir);
-
- archive = flatpak_oci_layer_writer_open (layer_writer, cancellable, error);
- if (archive == NULL)
+ layer_writer = flatpak_oci_registry_write_layer (registry, cancellable, error);
+ if (layer_writer == NULL)
return FALSE;
+ archive = flatpak_oci_layer_writer_get_archive (layer_writer);
+
if (!export_commit_to_archive (repo, root, ostree_commit_get_timestamp (commit_data),
archive, cancellable, error))
return FALSE;
if (!flatpak_oci_layer_writer_close (layer_writer,
- &layer_uncompressed_sha256,
- &layer_uncompressed_size,
- &layer_compressed_sha256,
- &layer_compressed_size,
- cancellable, error))
+ &uncompressed_digest,
+ &layer_ref,
+ cancellable,
+ error))
return FALSE;
- config = generate_config_json (ostree_commit_get_timestamp (commit_data),
- layer_uncompressed_sha256,
- ref_parts[2]);
- config_sha256 = flatpak_oci_dir_write_blob (oci_dir, config, cancellable, error);
- if (config_sha256 == NULL)
+
+ image = flatpak_oci_image_new ();
+ flatpak_oci_image_set_layer (image, uncompressed_digest);
+
+ timestamp = timestamp_to_iso8601 (ostree_commit_get_timestamp (commit_data));
+ flatpak_oci_image_set_created (image, timestamp);
+
+ image_ref = flatpak_oci_registry_store_json (registry, FLATPAK_JSON (image), cancellable, error);
+ if (image_ref == NULL)
return FALSE;
- manifest = generate_manifest_json (g_bytes_get_size (config), config_sha256,
- layer_compressed_size, layer_compressed_sha256,
- ref, commit_checksum, commit_data);
- manifest_sha256 = flatpak_oci_dir_write_blob (oci_dir, manifest, cancellable, error);
- if (manifest_sha256 == NULL)
+ manifest = flatpak_oci_manifest_new ();
+ flatpak_oci_manifest_set_config (manifest, image_ref);
+ flatpak_oci_manifest_set_layer (manifest, layer_ref);
+
+ annotations = flatpak_oci_manifest_get_annotations (manifest);
+ flatpak_oci_add_annotations_for_commit (annotations, ref, commit_checksum, commit_data);
+
+ metadata_file = g_file_get_child (root, "metadata");
+ if (g_file_load_contents (metadata_file, cancellable, &metadata_contents, &metadata_size, NULL, NULL) &&
+ g_utf8_validate (metadata_contents, -1, NULL))
+ {
+ g_hash_table_replace (annotations,
+ g_strdup ("org.flatpak.Metadata"),
+ g_steal_pointer (&metadata_contents));
+ }
+
+ if (!flatpak_repo_collect_sizes (repo, root, &installed_size, NULL, NULL, error))
return FALSE;
- if (!flatpak_oci_dir_set_ref (oci_dir, "latest",
- g_bytes_get_size (manifest), manifest_sha256,
- cancellable, error))
+ g_hash_table_replace (annotations,
+ g_strdup ("org.flatpak.InstalledSize"),
+ g_strdup_printf ("%" G_GUINT64_FORMAT, installed_size));
+
+ manifest_ref = flatpak_oci_registry_store_json (registry, FLATPAK_JSON (manifest), cancellable, error);
+ if (manifest_ref == NULL)
return FALSE;
- g_print ("WARNING: the oci format produced by flatpak is experimental and unstable.\n"
- "Don't use this for anything but experiments for now\n");
+ if (!flatpak_oci_registry_set_ref (registry, "latest", manifest_ref,
+ cancellable, error))
+ return FALSE;
return TRUE;
}
diff --git a/app/flatpak-builtins-build-import-bundle.c b/app/flatpak-builtins-build-import-bundle.c
index ad9fc8eb..5faebb57 100644
--- a/app/flatpak-builtins-build-import-bundle.c
+++ b/app/flatpak-builtins-build-import-bundle.c
@@ -31,7 +31,7 @@
#include "flatpak-builtins.h"
#include "flatpak-utils.h"
-#include "flatpak-oci.h"
+#include "flatpak-oci-registry.h"
static char *opt_ref;
static gboolean opt_oci = FALSE;
@@ -50,138 +50,46 @@ static GOptionEntry options[] = {
{ NULL }
};
-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
-propagate_libarchive_error (GError **error,
- struct archive *a)
-{
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "%s", archive_error_string (a));
-}
-
static char *
import_oci (OstreeRepo *repo, GFile *file,
GCancellable *cancellable, GError **error)
{
- g_autoptr(OstreeMutableTree) archive_mtree = NULL;
- g_autoptr(GFile) archive_root = NULL;
g_autofree char *commit_checksum = NULL;
- g_autofree char *config_digest = NULL;
- const char *parent = NULL;
- const char *metadata_base64;
- const char *subject;
- const char *body;
- const char *target_ref;
- guint64 timestamp;
- g_autoptr(FlatpakOciDir) oci_dir = NULL;
- g_autoptr(JsonObject) manifest = NULL;
- g_autoptr(JsonObject) config = NULL;
- g_autoptr(GHashTable) annotations = NULL;
- g_autoptr(GVariant) metadatav = NULL;
- g_auto(GStrv) layers = NULL;
- int i;
+ g_autofree char *dir_uri = NULL;
+ g_autofree char *target_ref = NULL;
+ g_autofree char *oci_digest = NULL;
+ g_autoptr(FlatpakOciRegistry) registry = NULL;
+ g_autoptr(FlatpakOciManifest) manifest = NULL;
+ GHashTable *annotations;
- oci_dir = flatpak_oci_dir_new ();
-
- if (!flatpak_oci_dir_open (oci_dir, file, cancellable, error))
+ dir_uri = g_file_get_uri (file);
+ registry = flatpak_oci_registry_new (dir_uri, FALSE, -1, cancellable, error);
+ if (registry == NULL)
return NULL;
- manifest = flatpak_oci_dir_find_manifest (oci_dir, "latest", "linux", "amd64",
- cancellable, error);
+ manifest = flatpak_oci_registry_chose_image (registry, "latest", &oci_digest,
+ cancellable, error);
if (manifest == NULL)
return NULL;
+ if (opt_ref)
+ target_ref = g_strdup (opt_ref);
+
annotations = flatpak_oci_manifest_get_annotations (manifest);
+ if (annotations)
+ flatpak_oci_parse_commit_annotations (annotations, NULL, NULL, NULL,
+ &target_ref, NULL, NULL, NULL);
- if (opt_ref != NULL)
- target_ref = opt_ref;
- else
+ if (target_ref == NULL)
{
- target_ref = g_hash_table_lookup (annotations, "org.flatpak.Ref");
- if (target_ref == NULL)
- {
- flatpak_fail (error, "No flatpak ref specified in image, must manually specify");
- return NULL;
- }
- }
-
- subject = g_hash_table_lookup (annotations, "org.flatpak.Subject");
- body = g_hash_table_lookup (annotations, "org.flatpak.Body");
- parent = g_hash_table_lookup (annotations, "org.flatpak.ParentCommit");
- metadata_base64 = g_hash_table_lookup (annotations, "org.flatpak.Metadata");
- if (metadata_base64)
- {
- gsize data_len;
- guchar *data = g_base64_decode (metadata_base64, &data_len);
- metadatav = g_variant_new_from_data (G_VARIANT_TYPE("a{sv}"), data, data_len,
- FALSE, g_free, data);
- }
-
- config_digest = flatpak_oci_manifest_get_config (manifest);
- if (config_digest == NULL)
- {
- flatpak_fail (error, "No oci config specified");
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "The OCI image didn't specify a ref, use --ref to specify one");
return NULL;
}
- config = flatpak_oci_dir_load_json (oci_dir, config_digest, cancellable, error);
- if (config == NULL)
- return NULL;
-
- timestamp = flatpak_oci_config_get_created (config);
-
- if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
- return NULL;
-
- /* There is no way to write a subset of the archive to a mtree, so instead
- we write all of it and then build a new mtree with the subset */
- archive_mtree = ostree_mutable_tree_new ();
-
- layers = flatpak_oci_manifest_get_layers (manifest);
- for (i = 0; layers[i] != NULL; i++)
- {
- OstreeRepoImportArchiveOptions opts = { 0, };
- free_read_archive struct archive *a = NULL;
-
- opts.autocreate_parents = TRUE;
-
- a = flatpak_oci_dir_load_layer (oci_dir, layers[i],
- cancellable, error);
- if (a == NULL)
- return NULL;
-
- if (!ostree_repo_import_archive_to_mtree (repo, &opts, a, archive_mtree, NULL, cancellable, error))
- return NULL;
-
- if (archive_read_close (a) != ARCHIVE_OK)
- {
- propagate_libarchive_error (error, a);
- return NULL;
- }
- }
-
- if (!ostree_repo_write_mtree (repo, archive_mtree, &archive_root, cancellable, error))
- return NULL;
-
- if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile *) archive_root, error))
- return NULL;
-
- if (!ostree_repo_write_commit_with_time (repo,
- parent,
- subject,
- body,
- metadatav,
- OSTREE_REPO_FILE (archive_root),
- timestamp,
- &commit_checksum,
- cancellable, error))
- return NULL;
-
- ostree_repo_transaction_set_ref (repo, NULL, target_ref, commit_checksum);
-
- if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+ commit_checksum = flatpak_pull_from_oci (repo, registry, oci_digest, manifest,
+ target_ref, cancellable, error);
+ if (commit_checksum == NULL)
return NULL;
g_print ("Importing %s (%s)\n", target_ref, commit_checksum);
diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c
index 7d02e810..432f74b9 100644
--- a/app/flatpak-builtins-install.c
+++ b/app/flatpak-builtins-install.c
@@ -49,6 +49,7 @@ static gboolean opt_runtime;
static gboolean opt_app;
static gboolean opt_bundle;
static gboolean opt_from;
+static gboolean opt_oci;
static GOptionEntry options[] = {
{ "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to install for"), N_("ARCH") },
@@ -60,6 +61,7 @@ static GOptionEntry options[] = {
{ "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL },
{ "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Install from local bundle file"), NULL },
{ "from", 0, 0, G_OPTION_ARG_NONE, &opt_from, N_("Load options from file or uri"), NULL },
+ { "oci", 0, 0, G_OPTION_ARG_NONE, &opt_oci, N_("Install from oci image"), NULL },
{ "gpg-file", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_gpg_file, N_("Check bundle signatures with GPG key from FILE (- for stdin)"), N_("FILE") },
{ "subpath", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_subpaths, N_("Only install this subpath"), N_("PATH") },
{ NULL }
@@ -310,6 +312,48 @@ install_from (FlatpakDir *dir,
return TRUE;
}
+static gboolean
+install_oci (FlatpakDir *dir,
+ GOptionContext *context,
+ int argc, char **argv,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const char *registry_arg;
+ g_autoptr(GFile) registry_file = NULL;
+ g_autofree char *registry_uri = NULL;
+ g_autoptr(FlatpakTransaction) transaction = NULL;
+ char *default_tags[] = { "latest", NULL };
+ char **tags;
+ int i;
+
+ if (argc < 2)
+ return usage_error (context, _("OCI repo Filename or uri must be specified"), error);
+
+ registry_arg = argv[1];
+ if (argc > 2)
+ tags = &argv[2];
+ else
+ tags = default_tags;
+
+ registry_file = g_file_new_for_commandline_arg (registry_arg);
+ registry_uri = g_file_get_uri (registry_file);
+
+ transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy,
+ !opt_no_deps, !opt_no_related);
+
+ for (i = 0; tags[i] != NULL; i++)
+ {
+ if (!flatpak_transaction_add_install_oci (transaction, registry_uri, tags[i], error))
+ return FALSE;
+ }
+
+ if (!flatpak_transaction_run (transaction, TRUE, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
gboolean
flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error)
{
@@ -337,6 +381,9 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro
if (opt_from)
return install_from (dir, context, argc, argv, cancellable, error);
+ if (opt_oci)
+ return install_oci (dir, context, argc, argv, cancellable, error);
+
if (argc < 3)
return usage_error (context, _("REMOTE and REF must be specified"), error);
diff --git a/app/flatpak-builtins-list.c b/app/flatpak-builtins-list.c
index cf74e871..6a348048 100644
--- a/app/flatpak-builtins-list.c
+++ b/app/flatpak-builtins-list.c
@@ -162,13 +162,14 @@ print_installed_refs (gboolean app, gboolean runtime, gboolean print_system, gbo
if (opt_show_details)
{
- g_autofree char *active = flatpak_dir_read_active (dir, ref, NULL);
+ const char *active = flatpak_deploy_data_get_commit (deploy_data);
+ const char *alt_id = flatpak_deploy_data_get_alt_id (deploy_data);
g_autofree char *latest = NULL;
g_autofree char *size_s = NULL;
guint64 size = 0;
g_autofree const char **subpaths = NULL;
- latest = flatpak_dir_read_latest (dir, repo, ref, NULL, NULL);
+ latest = flatpak_dir_read_latest (dir, repo, ref, NULL, NULL, NULL);
if (latest)
{
if (strcmp (active, latest) == 0)
@@ -176,10 +177,6 @@ print_installed_refs (gboolean app, gboolean runtime, gboolean print_system, gbo
g_free (latest);
latest = g_strdup ("-");
}
- else
- {
- latest[MIN (strlen (latest), 12)] = 0;
- }
}
else
{
@@ -189,9 +186,8 @@ print_installed_refs (gboolean app, gboolean runtime, gboolean print_system, gbo
flatpak_table_printer_add_column (printer, partial_ref);
flatpak_table_printer_add_column (printer, repo);
- active[MIN (strlen (active), 12)] = 0;
- flatpak_table_printer_add_column (printer, active);
- flatpak_table_printer_add_column (printer, latest);
+ flatpak_table_printer_add_column_len (printer, active, 12);
+ flatpak_table_printer_add_column_len (printer, latest, 12);
size = flatpak_deploy_data_get_installed_size (deploy_data);
size_s = g_format_size (size);
@@ -202,6 +198,12 @@ print_installed_refs (gboolean app, gboolean runtime, gboolean print_system, gbo
if (print_user && print_system)
flatpak_table_printer_append_with_comma (printer, is_user ? "user" : "system");
+ if (alt_id)
+ {
+ g_autofree char *alt_id_str = g_strdup_printf ("alt-id=%.12s", alt_id);
+ flatpak_table_printer_append_with_comma (printer, alt_id_str);
+ }
+
if (strcmp (parts[0], "app") == 0)
{
g_autofree char *current;
diff --git a/app/flatpak-oci.c b/app/flatpak-oci.c
deleted file mode 100644
index ae818c09..00000000
--- a/app/flatpak-oci.c
+++ /dev/null
@@ -1,1195 +0,0 @@
-/*
- * 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 "flatpak-utils.h"
-#include "flatpak-oci.h"
-
-struct FlatpakOciDir
-{
- GObject parent;
-
- int dfd;
-};
-
-typedef struct
-{
- GObjectClass parent_class;
-} FlatpakOciDirClass;
-
-G_DEFINE_TYPE (FlatpakOciDir, flatpak_oci_dir, G_TYPE_OBJECT)
-
-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 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;
-}
-
-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;
-}
-
-static int
-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 *
-load_file (int dfd, const char *subpath, GCancellable *cancellable, GError **error)
-{
- glnx_fd_close int fd = -1;
-
- fd = open_file (dfd, subpath, cancellable, error);
- if (fd == -1)
- return NULL;
-
- return glnx_fd_readall_bytes (fd, cancellable, error);
-}
-
-static JsonObject *
-load_json (int dfd, const char *subpath, GCancellable *cancellable, GError **error)
-{
- g_autoptr(GBytes) bytes = NULL;
- g_autoptr(JsonParser) parser = NULL;
- JsonNode *root = NULL;
-
- bytes = load_file (dfd, subpath, cancellable, error);
- if (bytes == NULL)
- return 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))
- {
- flatpak_fail (error, _("Invalid json, no root object"));
- return NULL;
- }
-
- return json_node_dup_object (root);
-}
-
-static void
-flatpak_oci_dir_finalize (GObject *object)
-{
- FlatpakOciDir *self = FLATPAK_OCI_DIR (object);
-
- if (self->dfd != -1)
- close (self->dfd);
-
- G_OBJECT_CLASS (flatpak_oci_dir_parent_class)->finalize (object);
-}
-
-static void
-flatpak_oci_dir_class_init (FlatpakOciDirClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- object_class->finalize = flatpak_oci_dir_finalize;
-
-}
-
-static void
-flatpak_oci_dir_init (FlatpakOciDir *self)
-{
-}
-
-FlatpakOciDir *
-flatpak_oci_dir_new (void)
-{
- FlatpakOciDir *oci_dir;
-
- oci_dir = g_object_new (FLATPAK_TYPE_OCI_DIR, NULL);
-
- return oci_dir;
-}
-
-static gboolean
-verify_oci_version (JsonObject *oci_layout, GError **error)
-{
- const char *version;
-
- version = json_object_get_string_member (oci_layout, "imageLayoutVersion");
- if (version == NULL)
- return flatpak_fail (error, _("Unsupported oci repo: oci-layout version missing"));
- if (strcmp (version, "1.0.0") != 0)
- return flatpak_fail (error, _("Unsupported existing oci-layout version %s (only 1.0.0 supported)"), version);
-
- return TRUE;
-}
-
-gboolean
-flatpak_oci_dir_open (FlatpakOciDir *self,
- GFile *dir,
- GCancellable *cancellable,
- GError **error)
-{
- glnx_fd_close int dfd = -1;
- g_autoptr(JsonObject) oci_layout = NULL;
- g_autoptr(GError) local_error = NULL;
-
- if (!glnx_opendirat (AT_FDCWD,
- flatpak_file_get_path_cached (dir),
- TRUE, &dfd, error))
- return FALSE;
-
- oci_layout = load_json (dfd, "oci-layout", cancellable, &local_error);
- if (oci_layout == NULL)
- {
- if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
- {
- g_propagate_error (error, g_steal_pointer (&local_error));
- return FALSE;
- }
-
- return flatpak_fail (error, _("Unsupported oci repo: oci-layout missing"));
- }
-
- if (!verify_oci_version (oci_layout, error))
- return FALSE;
-
- self->dfd = glnx_steal_fd (&dfd);
- return TRUE;
-}
-
-gboolean
-flatpak_oci_dir_ensure (FlatpakOciDir *self,
- GFile *dir,
- GCancellable *cancellable,
- GError **error)
-{
- glnx_fd_close int dfd = -1;
- g_autoptr(GFile) dir_blobs = NULL;
- g_autoptr(GFile) dir_blobs_sha256 = NULL;
- g_autoptr(GFile) dir_refs = NULL;
- g_autoptr(JsonObject) oci_layout = NULL;
- g_autoptr(GError) local_error = NULL;
-
- dir_blobs = g_file_get_child (dir, "blobs");
- dir_blobs_sha256 = g_file_get_child (dir_blobs, "sha256");
- dir_refs = g_file_get_child (dir, "refs");
-
- if (!flatpak_mkdir_p (dir_blobs_sha256, cancellable, error))
- return FALSE;
-
- if (!flatpak_mkdir_p (dir_refs, cancellable, error))
- return FALSE;
-
- if (!glnx_opendirat (AT_FDCWD,
- flatpak_file_get_path_cached (dir),
- TRUE, &dfd, error))
- return FALSE;
-
- oci_layout = load_json (dfd, "oci-layout", cancellable, &local_error);
- if (oci_layout == NULL)
- {
- const char *new_layout_data = "{\"imageLayoutVersion\": \"1.0.0\"}";
-
- if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
- {
- g_propagate_error (error, g_steal_pointer (&local_error));
- return FALSE;
- }
-
- if (!glnx_file_replace_contents_at (dfd, "oci-layout",
- (const guchar *)new_layout_data,
- strlen (new_layout_data),
- 0,
- cancellable, error))
- return FALSE;
- }
- else
- {
- if (!verify_oci_version (oci_layout, error))
- return FALSE;
- }
-
- self->dfd = glnx_steal_fd (&dfd);
- return TRUE;
-}
-
-char *
-flatpak_oci_dir_write_blob (FlatpakOciDir *self,
- GBytes *data,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree char *sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, data);
- g_autofree char *path = g_strdup_printf ("blobs/sha256/%s", sha256);
-
- if (!glnx_file_replace_contents_at (self->dfd, path,
- g_bytes_get_data (data, NULL),
- g_bytes_get_size (data),
- 0, cancellable, error))
- return NULL;
-
- return g_steal_pointer (&sha256);
-}
-
-static GBytes *
-generate_ref_json (guint64 manifest_size,
- const char *manifest_sha256)
-{
- g_autoptr(FlatpakJsonWriter) writer = flatpak_json_writer_new ();
- g_autofree char *manifest_digest = g_strdup_printf ("sha256:%s", manifest_sha256);
-
- flatpak_json_writer_add_uint64_property (writer, "size", manifest_size);
- flatpak_json_writer_add_string_property (writer, "digest", manifest_digest);
- flatpak_json_writer_add_string_property (writer, "mediaType", "application/vnd.oci.image.manifest.v1+json");
-
- return flatpak_json_writer_get_result (writer);
-}
-
-
-gboolean
-flatpak_oci_dir_set_ref (FlatpakOciDir *self,
- const char *ref,
- guint64 object_size,
- const char *object_sha256,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree char *path = g_strdup_printf ("refs/%s", ref);
- g_autoptr(GBytes) data = NULL;
-
- data = generate_ref_json (object_size, object_sha256);
- if (!glnx_file_replace_contents_at (self->dfd, path,
- g_bytes_get_data (data, NULL),
- g_bytes_get_size (data),
- 0, cancellable, error))
- return FALSE;
-
- return TRUE;
-}
-
-GBytes *
-flatpak_oci_dir_load_object (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree char *path = NULL;
-
- if (!g_str_has_prefix (digest, "sha256:"))
- {
- flatpak_fail (error, "Unsupported digest type %s", digest);
- return NULL;
- }
-
- path = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:"));
- return load_file (self->dfd, path, cancellable, error);
-}
-
-JsonObject *
-flatpak_oci_dir_load_json (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(GBytes) bytes = NULL;
- g_autoptr(JsonParser) parser = NULL;
- JsonNode *root = NULL;
-
- bytes = flatpak_oci_dir_load_object (self, digest, cancellable, error);
- if (bytes == NULL)
- return 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))
- {
- flatpak_fail (error, _("Invalid json, no root object"));
- return NULL;
- }
-
- return json_node_dup_object (root);
-}
-
-struct archive *
-flatpak_oci_dir_load_layer (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree char *path = NULL;
- glnx_fd_close int fd = -1;
- free_read_archive struct archive *a = NULL;
-
- if (!g_str_has_prefix (digest, "sha256:"))
- {
- flatpak_fail (error, "Unsupported digest type %s", digest);
- return NULL;
- }
-
- path = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:"));
-
- fd = open_file (self->dfd, path, cancellable, error);
- if (fd == -1)
- return NULL;
-
- a = archive_read_new ();
-#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
- archive_read_support_filter_all (a);
-#else
- archive_read_support_compression_all (a);
-#endif
- archive_read_support_format_all (a);
- if (archive_read_open_fd (a, fd, 8192) != ARCHIVE_OK)
- {
- propagate_libarchive_error (error, a);
- return NULL;
- }
-
- fd = -1;
- return (struct archive *)g_steal_pointer (&a);
-}
-
-
-gboolean
-flatpak_oci_dir_load_ref (FlatpakOciDir *self,
- const char *ref,
- guint64 *size_out,
- char **digest_out,
- char **mediatype_out,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(JsonObject) ref_root = NULL;
- g_autofree char *path = g_strdup_printf ("refs/%s", ref);
- const char *mediatype, *digest;
- double size;
-
- ref_root = load_json (self->dfd, path, cancellable, error);
- if (ref_root == NULL)
- return FALSE;
-
- if (!json_object_has_member (ref_root, "mediaType") ||
- !json_object_has_member (ref_root, "digest") ||
- !json_object_has_member (ref_root, "size"))
- return flatpak_fail (error, _("Invalid ref format"));
-
- mediatype = json_object_get_string_member (ref_root, "mediaType");
- digest = json_object_get_string_member (ref_root, "digest");
- size = json_object_get_double_member (ref_root, "size");
-
- if (mediatype == NULL)
- return flatpak_fail (error, _("Invalid ref, no media type"));
- if (digest == NULL)
- return flatpak_fail (error, _("Invalid ref, no digest"));
-
- if (size_out)
- *size_out = (guint64)size;
- if (digest_out)
- *digest_out = g_strdup (digest);
- if (mediatype_out)
- *mediatype_out = g_strdup (mediatype);
- return TRUE;
-}
-
-JsonObject *
-find_manifest (FlatpakOciDir *self,
- const char *digest,
- const char *os,
- const char *arch,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(JsonObject) manifest = NULL;
- const char *mediatype;
- int version;
-
- manifest = flatpak_oci_dir_load_json (self, digest, cancellable, error);
- if (manifest == NULL)
- return NULL;
-
- mediatype = json_object_get_string_member (manifest, "mediaType");
- if (strcmp (mediatype, "application/vnd.oci.image.manifest.v1+json") != 0)
- {
- flatpak_fail (error, _("Unexpected media type %s, expected application/vnd.oci.image.manifest.v1+json"), mediatype);
- return NULL;
- }
-
- version = json_object_get_int_member (manifest, "schemaVersion");
- if (version != 2)
- {
- flatpak_fail (error, _("Unsupported manifest version %d"), version);
- return NULL;
- }
-
- return g_steal_pointer (&manifest);
-}
-
-JsonObject *
-find_manifest_list (FlatpakOciDir *self,
- const char *digest,
- const char *os,
- const char *arch,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(JsonObject) list = NULL;
- g_autoptr(JsonArray) manifests = NULL;
- int version;
- const char *mediatype;
- guint i, n_elements;
-
- list = flatpak_oci_dir_load_json (self, digest, cancellable, error);
- if (list == NULL)
- return NULL;
-
- mediatype = json_object_get_string_member (list, "mediaType");
- if (strcmp (mediatype, "application/vnd.oci.image.manifest.list.v1+json") != 0)
- {
- flatpak_fail (error, _("Unexpected media type %s, expected application/vnd.oci.image.manifest.list.v1+json"), mediatype);
- return NULL;
- }
-
- version = json_object_get_int_member (list, "schemaVersion");
- if (version != 2)
- {
- flatpak_fail (error, _("Unsupported manifest list version %d"), version);
- return NULL;
- }
-
- manifests = json_object_get_array_member (list, "manifests");
- if (manifests == NULL)
- {
- flatpak_fail (error, _("Missing element 'manifests'"), version);
- return NULL;
- }
-
- n_elements = json_array_get_length (manifests);
- for (i = 0; i < n_elements; i++)
- {
- JsonObject *manifest = json_array_get_object_element (manifests, i);
- JsonObject *platform;
- const char *element_arch, *element_os, *mediatype, *element_digest;
-
- if (manifest == NULL)
- continue;
-
- mediatype = json_object_get_string_member (list, "mediaType");
- if (strcmp (mediatype, "application/vnd.oci.image.manifest.v1+json") != 0)
- continue;
-
- element_digest = json_object_get_string_member (list, "digest");
- if (element_digest == NULL)
- continue;
-
- platform = json_object_get_object_member (list, "platform");
- if (platform == NULL)
- continue;
-
- element_arch = json_object_get_string_member (list, "architecture");
- element_os = json_object_get_string_member (list, "mediaType");
- if (g_strcmp0 (arch, element_arch) == 0 &&
- g_strcmp0 (os, element_os) == 0)
- {
- return find_manifest (self, element_digest, os, arch, cancellable, error);
- }
- }
-
- flatpak_fail (error, _("No manfest found for arch %s, os %s"), arch, os);
- return NULL;
-}
-
-JsonObject *
-flatpak_oci_dir_find_manifest (FlatpakOciDir *self,
- const char *ref,
- const char *os,
- const char *arch,
- GCancellable *cancellable,
- GError **error)
-{
- g_autofree char *digest = NULL;
- g_autofree char *mediatype = NULL;
-
- if (!flatpak_oci_dir_load_ref (self, ref, NULL, &digest, &mediatype,
- cancellable, error))
- return NULL;
-
- if (strcmp (mediatype, "application/vnd.oci.image.manifest.list.v1+json") == 0)
- return find_manifest_list (self, digest, os, arch, cancellable, error);
- else if (strcmp (mediatype, "application/vnd.oci.image.manifest.v1+json") == 0)
- return find_manifest (self, digest, os, arch, cancellable, error);
- else
- {
- flatpak_fail (error, _("Unsupported OCI media type %s"), mediatype);
- return NULL;
- }
-}
-
-char *
-flatpak_oci_manifest_get_config (JsonObject *manifest)
-{
- JsonObject *config = NULL;
- const char *mediatype, *digest;
-
- config = json_object_get_object_member (manifest, "config");
-
- mediatype = json_object_get_string_member (config, "mediaType");
- if (mediatype == NULL)
- return NULL;
-
- if (strcmp (mediatype, "application/vnd.oci.image.config.v1+json") != 0)
- return NULL;
-
- digest = json_object_get_string_member (config, "digest");
-
- return g_strdup (digest);
-}
-
-char **
-flatpak_oci_manifest_get_layers (JsonObject *manifest)
-{
- JsonArray *layers = NULL;
- guint i, n_elements;
- g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free);
-
- layers = json_object_get_array_member (manifest, "layers");
-
- n_elements = json_array_get_length (layers);
- for (i = 0; i < n_elements; i++)
- {
- JsonObject *layer = json_array_get_object_element (layers, i);
- const char *digest, *mediatype;
-
- mediatype = json_object_get_string_member (layer, "mediaType");
- if (strcmp (mediatype, "application/vnd.oci.image.layer.v1.tar+gzip") != 0)
- continue;
-
- digest = json_object_get_string_member (layer, "digest");
- if (digest == NULL)
- continue;
-
- g_ptr_array_add (res, g_strdup (digest));
- }
-
- g_ptr_array_add (res, NULL);
-
- return (char **)g_ptr_array_free (g_steal_pointer (&res), FALSE);
-}
-
-GHashTable *
-flatpak_oci_manifest_get_annotations (JsonObject *manifest)
-{
- JsonObject *annotations = NULL;
- g_autoptr(GHashTable) res = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
- GList *members, *l;
-
- annotations = json_object_get_object_member (manifest, "annotations");
-
- members = json_object_get_members (annotations);
- for (l = members; l != NULL; l = l->next)
- {
- const char *member = l->data;
- const char *value;
-
- value = json_object_get_string_member (annotations, member);
- g_hash_table_insert (res, g_strdup (member), g_strdup (value));
- }
- g_list_free (members);
-
- return g_steal_pointer (&res);
-}
-
-guint64
-flatpak_oci_config_get_created (JsonObject *config)
-{
- const char *created;
- GTimeVal tv;
-
- created = json_object_get_string_member (config, "created");
- if (created == NULL)
- return 0;
-
- if (g_time_val_from_iso8601 (created, &tv))
- return tv.tv_sec;
- else
- return 0;
-}
-
-/*************************************************************************/
-
-struct FlatpakOciLayerWriter
-{
- GObject parent;
-
- FlatpakOciDir *dir;
-
- 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 void
-flatpak_oci_layer_writer_reset (FlatpakOciLayerWriter *self)
-{
- if (self->tmp_path)
- {
- (void) unlinkat (self->dir->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->dir);
-
- 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);
-}
-
-FlatpakOciLayerWriter *
-flatpak_oci_layer_writer_new (FlatpakOciDir *dir)
-{
- FlatpakOciLayerWriter *oci_layer_writer;
-
- oci_layer_writer = g_object_new (FLATPAK_TYPE_OCI_LAYER_WRITER, NULL);
- oci_layer_writer->dir = g_object_ref (dir);
-
- return oci_layer_writer;
-}
-
-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;
-}
-
-struct archive *
-flatpak_oci_layer_writer_open (FlatpakOciLayerWriter *self,
- GCancellable *cancellable,
- GError **error)
-{
- free_write_archive struct archive *a = NULL;
- glnx_fd_close int tmp_fd = -1;
- g_autofree char *tmp_path = NULL;
-
- if (!glnx_open_tmpfile_linkable_at (self->dir->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, self,
- 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 (self);
-
- self->archive = g_steal_pointer (&a);
- self->tmp_fd = glnx_steal_fd (&tmp_fd);
- self->tmp_path = g_steal_pointer (&tmp_path);
- self->compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
-
- return self->archive;
-}
-
-gboolean
-flatpak_oci_layer_writer_close (FlatpakOciLayerWriter *self,
- char **uncompressed_sha256_out,
- guint64 *uncompressed_size_out,
- char **compressed_sha256_out,
- guint64 *compressed_size_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->dir->dfd,
- GLNX_LINK_TMPFILE_REPLACE,
- self->tmp_fd,
- self->tmp_path,
- self->dir->dfd,
- path,
- error))
- return FALSE;
-
- close (self->tmp_fd);
- self->tmp_fd = -1;
- g_free (self->tmp_path);
- self->tmp_path = NULL;
-
- if (uncompressed_sha256_out != NULL)
- *uncompressed_sha256_out = g_strdup (g_checksum_get_string (self->uncompressed_checksum));
- if (uncompressed_size_out != NULL)
- *uncompressed_size_out = self->uncompressed_size;
- if (compressed_sha256_out != NULL)
- *compressed_sha256_out = g_strdup (g_checksum_get_string (self->compressed_checksum));
- if (compressed_size_out != NULL)
- *compressed_size_out = self->compressed_size;
-
- return TRUE;
-}
-
-/*************************************************************************/
-
-typedef struct JsonScope JsonScope;
-
-struct JsonScope {
- JsonScope *parent;
- int index;
- char end_char;
-};
-
-struct FlatpakJsonWriter
-{
- GString *str;
- int depth;
- JsonScope *scope;
-};
-
-FlatpakJsonWriter *
-flatpak_json_writer_new ()
-{
- FlatpakJsonWriter *self = g_new0 (FlatpakJsonWriter, 1);
- self->str = g_string_new ("");
-
- flatpak_json_writer_open_struct (self);
-
- return self;
-}
-
-GBytes *
-flatpak_json_writer_get_result (FlatpakJsonWriter *self)
-{
- GBytes *res = NULL;
-
- if (self->str)
- {
- flatpak_json_writer_close (self);
- res = g_string_free_to_bytes (self->str);
- self->str = NULL;
- }
-
- return res;
-}
-
-void
-flatpak_json_writer_free (FlatpakJsonWriter *self)
-{
- if (self->str)
- g_string_free (self->str, TRUE);
- g_free (self);
-}
-
-static void
-flatpak_json_writer_indent (FlatpakJsonWriter *self)
-{
- int i;
-
- for (i = 0; i < self->depth; i++)
- g_string_append (self->str, " ");
-}
-
-static void
-flatpak_json_writer_add_bool (FlatpakJsonWriter *self, gboolean val)
-{
- if (val)
- g_string_append (self->str, "true");
- else
- g_string_append (self->str, "false");
-}
-
-static void
-flatpak_json_writer_add_uint64 (FlatpakJsonWriter *self, guint64 val)
-{
- g_string_append_printf (self->str, "%"G_GUINT64_FORMAT, val);
-}
-
-
-static void
-flatpak_json_writer_add_string (FlatpakJsonWriter *self, const gchar *str)
-{
- const gchar *p;
-
- g_string_append_c (self->str, '"');
-
- for (p = str; *p != 0; p++)
- {
- if (*p == '\\' || *p == '"')
- {
- g_string_append_c (self->str, '\\');
- g_string_append_c (self->str, *p);
- }
- else if ((*p > 0 && *p < 0x1f) || *p == 0x7f)
- {
- switch (*p)
- {
- case '\b':
- g_string_append (self->str, "\\b");
- break;
-
- case '\f':
- g_string_append (self->str, "\\f");
- break;
-
- case '\n':
- g_string_append (self->str, "\\n");
- break;
-
- case '\r':
- g_string_append (self->str, "\\r");
- break;
-
- case '\t':
- g_string_append (self->str, "\\t");
- break;
-
- default:
- g_string_append_printf (self->str, "\\u00%02x", (guint) * p);
- break;
- }
- }
- else
- {
- g_string_append_c (self->str, *p);
- }
- }
-
- g_string_append_c (self->str, '"');
-}
-
-static void
-flatpak_json_writer_start_item (FlatpakJsonWriter *self)
-{
- int index = self->scope->index;
-
- if (index != 0)
- g_string_append (self->str, ",\n");
- else
- g_string_append (self->str, "\n");
- flatpak_json_writer_indent (self);
- self->scope->index = index + 1;
-}
-
-static void
-flatpak_json_writer_open_scope (FlatpakJsonWriter *self,
- char start_char,
- char end_char)
-{
- JsonScope *scope = g_new0 (JsonScope, 1);
-
- scope->parent = self->scope;
- scope->end_char = end_char;
-
- self->scope = scope;
- self->depth += 1;
-
- g_string_append_c (self->str, start_char);
-}
-
-void
-flatpak_json_writer_close (FlatpakJsonWriter *self)
-{
- JsonScope *scope;
-
- scope = self->scope;
- self->scope = scope->parent;
- self->depth -= 1;
-
- g_string_append (self->str, "\n");
- flatpak_json_writer_indent (self);
- g_string_append_c (self->str, scope->end_char);
-
- g_free (scope);
-
- /* Last newline in file */
- if (self->scope == NULL)
- g_string_append (self->str, "\n");
-}
-
-void
-flatpak_json_writer_open_struct (FlatpakJsonWriter *self)
-{
- flatpak_json_writer_open_scope (self, '{', '}');
-}
-
-void
-flatpak_json_writer_open_array (FlatpakJsonWriter *self)
-{
- flatpak_json_writer_open_scope (self, '[', ']');
-}
-
-static void
-flatpak_json_writer_add_property (FlatpakJsonWriter *self, const gchar *name)
-{
- flatpak_json_writer_start_item (self);
- flatpak_json_writer_add_string (self, name);
- g_string_append (self->str, ": ");
-}
-
-void
-flatpak_json_writer_add_struct_property (FlatpakJsonWriter *self, const gchar *name)
-{
- flatpak_json_writer_add_property (self, name);
- flatpak_json_writer_open_struct (self);
-}
-
-void
-flatpak_json_writer_add_array_property (FlatpakJsonWriter *self, const gchar *name)
-{
- flatpak_json_writer_add_property (self, name);
- flatpak_json_writer_open_array (self);
-}
-
-void
-flatpak_json_writer_add_string_property (FlatpakJsonWriter *self, const gchar *name, const char *value)
-{
- flatpak_json_writer_add_property (self, name);
- flatpak_json_writer_add_string (self, value);
-}
-
-void
-flatpak_json_writer_add_uint64_property (FlatpakJsonWriter *self, const gchar *name, guint64 value)
-{
- flatpak_json_writer_add_property (self, name);
- flatpak_json_writer_add_uint64 (self, value);
-}
-
-void
-flatpak_json_writer_add_bool_property (FlatpakJsonWriter *self, const gchar *name, gboolean value)
-{
- flatpak_json_writer_add_property (self, name);
- flatpak_json_writer_add_bool (self, value);
-}
-
-void
-flatpak_json_writer_add_array_string (FlatpakJsonWriter *self, const gchar *string)
-{
- flatpak_json_writer_start_item (self);
- flatpak_json_writer_add_string (self, string);
-}
-
-void
-flatpak_json_writer_add_array_struct (FlatpakJsonWriter *self)
-{
- flatpak_json_writer_start_item (self);
- flatpak_json_writer_open_struct (self);
-}
diff --git a/app/flatpak-oci.h b/app/flatpak-oci.h
deleted file mode 100644
index 31314910..00000000
--- a/app/flatpak-oci.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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_H__
-#define __FLATPAK_OCI_H__
-
-#include "libglnx/libglnx.h"
-
-#include
-#include
-#include
-#include
-#include
-
-#define FLATPAK_TYPE_OCI_DIR flatpak_oci_dir_get_type ()
-#define FLATPAK_OCI_DIR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_OCI_DIR, FlatpakOciDir))
-#define FLATPAK_IS_OCI_DIR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_OCI_DIR))
-
-GType flatpak_oci_dir_get_type (void);
-
-typedef struct FlatpakOciDir FlatpakOciDir;
-
-#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 (FlatpakOciDir, g_object_unref)
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakOciLayerWriter, g_object_unref)
-
-const char * flatpak_arch_to_oci_arch (const char *flatpak_arch);
-
-FlatpakOciDir *flatpak_oci_dir_new (void);
-gboolean flatpak_oci_dir_open (FlatpakOciDir *self,
- GFile *dir,
- GCancellable *cancellable,
- GError **error);
-gboolean flatpak_oci_dir_ensure (FlatpakOciDir *self,
- GFile *dir,
- GCancellable *cancellable,
- GError **error);
-char * flatpak_oci_dir_write_blob (FlatpakOciDir *self,
- GBytes *data,
- GCancellable *cancellable,
- GError **error);
-gboolean flatpak_oci_dir_set_ref (FlatpakOciDir *self,
- const char *ref,
- guint64 object_size,
- const char *object_sha256,
- GCancellable *cancellable,
- GError **error);
-gboolean flatpak_oci_dir_load_ref (FlatpakOciDir *self,
- const char *ref,
- guint64 *size_out,
- char **digest_out,
- char **mediatype_out,
- GCancellable *cancellable,
- GError **error);
-GBytes * flatpak_oci_dir_load_object (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error);
-struct archive *flatpak_oci_dir_load_layer (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error);
-JsonObject * flatpak_oci_dir_load_json (FlatpakOciDir *self,
- const char *digest,
- GCancellable *cancellable,
- GError **error);
-JsonObject * flatpak_oci_dir_find_manifest (FlatpakOciDir *self,
- const char *ref,
- const char *os,
- const char *arch,
- GCancellable *cancellable,
- GError **error);
-
-char * flatpak_oci_manifest_get_config (JsonObject *manifest);
-char ** flatpak_oci_manifest_get_layers (JsonObject *manifest);
-GHashTable *flatpak_oci_manifest_get_annotations (JsonObject *manifest);
-
-guint64 flatpak_oci_config_get_created (JsonObject *config);
-
-FlatpakOciLayerWriter *flatpak_oci_layer_writer_new (FlatpakOciDir *dir);
-struct archive * flatpak_oci_layer_writer_open (FlatpakOciLayerWriter *self,
- GCancellable *cancellable,
- GError **error);
-gboolean flatpak_oci_layer_writer_close (FlatpakOciLayerWriter *self,
- char **uncompressed_sha256_out,
- guint64 *uncompressed_size_out,
- char **compressed_sha256_out,
- guint64 *compressed_size_out,
- GCancellable *cancellable,
- GError **error);
-
-
-
-typedef struct FlatpakJsonWriter FlatpakJsonWriter;
-
-FlatpakJsonWriter *flatpak_json_writer_new (void);
-GBytes *flatpak_json_writer_get_result (FlatpakJsonWriter *self);
-void flatpak_json_writer_free (FlatpakJsonWriter *self);
-
-void flatpak_json_writer_open_struct (FlatpakJsonWriter *writer);
-void flatpak_json_writer_open_array (FlatpakJsonWriter *writer);
-void flatpak_json_writer_close (FlatpakJsonWriter *writer);
-void flatpak_json_writer_add_struct_property (FlatpakJsonWriter *writer, const gchar *name);
-void flatpak_json_writer_add_array_property (FlatpakJsonWriter *writer, const gchar *name);
-void flatpak_json_writer_add_string_property (FlatpakJsonWriter *writer, const gchar *name, const char *value);
-void flatpak_json_writer_add_uint64_property (FlatpakJsonWriter *writer, const gchar *name, guint64 value);
-void flatpak_json_writer_add_bool_property (FlatpakJsonWriter *writer, const gchar *name, gboolean value);
-void flatpak_json_writer_add_array_string (FlatpakJsonWriter *writer, const gchar *string);
-void flatpak_json_writer_add_array_struct (FlatpakJsonWriter *writer);
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakJsonWriter, flatpak_json_writer_free)
-
-
-#endif /* __FLATPAK_OCI_H__ */
diff --git a/app/flatpak-transaction.c b/app/flatpak-transaction.c
index b4796798..617fa6b3 100644
--- a/app/flatpak-transaction.c
+++ b/app/flatpak-transaction.c
@@ -25,6 +25,7 @@
#include "flatpak-transaction.h"
#include "flatpak-utils.h"
#include "flatpak-builtins-utils.h"
+#include "flatpak-oci-registry.h"
#include "flatpak-error.h"
typedef struct FlatpakTransactionOp FlatpakTransactionOp;
@@ -458,6 +459,66 @@ flatpak_transaction_add_install (FlatpakTransaction *self,
return flatpak_transaction_add_ref (self, remote, ref, subpaths, NULL, FALSE, error);
}
+gboolean
+flatpak_transaction_add_install_oci (FlatpakTransaction *self,
+ const char *uri,
+ const char *tag,
+ GError **error)
+{
+ GHashTable *annotations;
+ g_autofree char *ref = NULL;
+ g_autofree char *checksum = NULL;
+ g_autoptr(FlatpakOciManifest) manifest = NULL;
+ g_autoptr(FlatpakOciRegistry) registry = NULL;
+ const char *all_paths[] = { NULL };
+ g_autofree char *remote = NULL;
+ g_autofree char *title = NULL;
+ g_autofree char **parts = NULL;
+ g_autofree char *id = NULL;
+
+ registry = flatpak_oci_registry_new (uri, FALSE, -1, NULL, error);
+ if (registry == NULL)
+ return FALSE;
+
+ manifest = flatpak_oci_registry_chose_image (registry, tag, NULL,
+ NULL, error);
+ if (manifest == NULL)
+ return FALSE;
+
+ /* TODO: Extract runtime dependencies and related refs */
+ annotations = flatpak_oci_manifest_get_annotations (manifest);
+ if (annotations)
+ flatpak_oci_parse_commit_annotations (annotations, NULL,
+ NULL, NULL,
+ &ref, &checksum, NULL,
+ NULL);
+
+ if (ref == NULL)
+ return flatpak_fail (error, _("OCI image is not a flatpak (missing ref)"));
+
+ parts = flatpak_decompose_ref (ref, error);
+ if (parts == NULL)
+ return FALSE;
+
+ title = g_strdup_printf ("OCI remote for %s", parts[1]);
+
+ id = g_strdup_printf ("oci-%s", parts[1]);
+
+ remote = flatpak_dir_create_origin_remote (self->dir, NULL,
+ id, title,
+ ref, uri, tag, NULL,
+ NULL, error);
+ if (remote == NULL)
+ return FALSE;
+
+ if (!flatpak_dir_recreate_repo (self->dir, NULL, error))
+ return FALSE;
+
+ g_debug ("Added OCI origin remote %s", remote);
+
+ return flatpak_transaction_add_ref (self, remote, ref, all_paths, checksum, FALSE, error);
+}
+
gboolean
flatpak_transaction_add_update (FlatpakTransaction *self,
const char *ref,
@@ -468,7 +529,6 @@ flatpak_transaction_add_update (FlatpakTransaction *self,
return flatpak_transaction_add_ref (self, NULL, ref, subpaths, commit, TRUE, error);
}
-
gboolean
flatpak_transaction_run (FlatpakTransaction *self,
gboolean stop_on_first_error,
diff --git a/app/flatpak-transaction.h b/app/flatpak-transaction.h
index c70964f4..6f548814 100644
--- a/app/flatpak-transaction.h
+++ b/app/flatpak-transaction.h
@@ -43,6 +43,10 @@ gboolean flatpak_transaction_add_install (FlatpakTransaction *self,
const char *ref,
const char **subpaths,
GError **error);
+gboolean flatpak_transaction_add_install_oci (FlatpakTransaction *self,
+ const char *uri,
+ const char *tag,
+ GError **error);
gboolean flatpak_transaction_add_update (FlatpakTransaction *self,
const char *ref,
const char **subpaths,
diff --git a/builder/builder-context.c b/builder/builder-context.c
index 2b86a5b2..9c1b55c3 100644
--- a/builder/builder-context.c
+++ b/builder/builder-context.c
@@ -230,25 +230,7 @@ SoupSession *
builder_context_get_soup_session (BuilderContext *self)
{
if (self->soup_session == NULL)
- {
- const char *http_proxy;
-
- self->soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "flatpak-builder ",
- SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
- SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
- SOUP_SESSION_TIMEOUT, 60,
- SOUP_SESSION_IDLE_TIMEOUT, 60,
- NULL);
- http_proxy = g_getenv ("http_proxy");
- if (http_proxy)
- {
- g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy);
- if (!proxy_uri)
- g_warning ("Invalid proxy URI '%s'", http_proxy);
- else
- g_object_set (self->soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
- }
- }
+ self->soup_session = flatpak_create_soup_session ("flatpak-builder");
return self->soup_session;
}
diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc
index 8e81b0ef..4d09f66b 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-common-types.h b/common/flatpak-common-types.h
index 11b79ae0..22657850 100644
--- a/common/flatpak-common-types.h
+++ b/common/flatpak-common-types.h
@@ -29,5 +29,7 @@ typedef enum {
typedef struct FlatpakDir FlatpakDir;
typedef struct FlatpakDeploy FlatpakDeploy;
typedef struct FlatpakContext FlatpakContext;
+typedef struct FlatpakOciRegistry FlatpakOciRegistry;
+typedef struct _FlatpakOciManifest FlatpakOciManifest;
#endif /* __FLATPAK_COMMON_TYPES_H__ */
diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c
index 9a843134..5d3701a0 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -37,6 +37,7 @@
#include "flatpak-dir.h"
#include "flatpak-utils.h"
+#include "flatpak-oci-registry.h"
#include "flatpak-run.h"
#include "errno.h"
@@ -686,6 +687,17 @@ flatpak_deploy_data_get_commit (GVariant *deploy_data)
return commit;
}
+const char *
+flatpak_deploy_data_get_alt_id (GVariant *deploy_data)
+{
+ g_autoptr(GVariant) metadata = g_variant_get_child_value (deploy_data, 4);
+ const char *alt_id = NULL;
+
+ g_variant_lookup (metadata, "alt-id", "&s", &alt_id);
+
+ return alt_id;
+}
+
/**
* flatpak_deploy_data_get_subpaths:
*
@@ -1342,29 +1354,11 @@ repo_pull_one_dir (OstreeRepo *self,
static void
ensure_soup_session (FlatpakDir *self)
{
- const char *http_proxy;
-
if (g_once_init_enter (&self->soup_session))
{
SoupSession *soup_session;
- soup_session =
- soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
- SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
- SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
- SOUP_SESSION_TIMEOUT, 60,
- SOUP_SESSION_IDLE_TIMEOUT, 60,
- NULL);
- http_proxy = g_getenv ("http_proxy");
- if (http_proxy)
- {
- g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy);
-
- if (!proxy_uri)
- g_warning ("Invalid proxy URI '%s'", http_proxy);
- else
- g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
- }
+ soup_session = flatpak_create_soup_session ("ostree");
if (g_getenv ("OSTREE_DEBUG_HTTP"))
soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500));
@@ -1550,6 +1544,70 @@ flatpak_dir_pull_extra_data (FlatpakDir *self,
return TRUE;
}
+static gboolean
+flatpak_dir_pull_oci (FlatpakDir *self,
+ const char *remote,
+ const char *ref,
+ OstreeRepo *repo,
+ FlatpakPullFlags flatpak_flags,
+ OstreeRepoPullFlags flags,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *annotations;
+ g_autoptr(FlatpakOciManifest) manifest = NULL;
+ g_autoptr(FlatpakOciRegistry) registry = NULL;
+ g_autofree char *oci_ref = NULL;
+ g_autofree char *oci_uri = NULL;
+ g_autofree char *oci_tag = NULL;
+ g_autofree char *oci_digest = NULL;
+ g_autofree char *full_ref = NULL;
+ g_autofree char *checksum = NULL;
+
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, remote);
+ g_assert (oci_uri != NULL);
+
+ oci_tag = flatpak_dir_get_remote_oci_tag (self, remote);
+ if (oci_tag == NULL)
+ oci_tag = g_strdup ("latest");
+
+ registry = flatpak_oci_registry_new (oci_uri, FALSE, -1, NULL, error);
+ if (registry == NULL)
+ return FALSE;
+
+ manifest = flatpak_oci_registry_chose_image (registry, oci_tag, &oci_digest,
+ NULL, error);
+ if (manifest == NULL)
+ return FALSE;
+
+ annotations = flatpak_oci_manifest_get_annotations (manifest);
+ if (annotations)
+ flatpak_oci_parse_commit_annotations (annotations, NULL,
+ NULL, NULL,
+ &oci_ref, NULL, NULL,
+ NULL);
+
+ if (oci_ref == NULL)
+ return flatpak_fail (error, _("OCI image is not a flatpak (missing ref)"));
+
+ if (strcmp (ref, oci_ref) != 0)
+ return flatpak_fail (error, _("OCI image specifies the wrong app id"));
+
+ full_ref = g_strdup_printf ("%s:%s", remote, ref);
+
+ if (repo == NULL)
+ repo = self->repo;
+
+ checksum = flatpak_pull_from_oci (repo, registry, oci_digest, manifest, full_ref, cancellable, error);
+ if (checksum == NULL)
+ return FALSE;
+
+ g_debug ("Imported OCI image as checksum %s\n", checksum);
+
+ return TRUE;
+}
+
gboolean
flatpak_dir_pull (FlatpakDir *self,
const char *repository,
@@ -1568,6 +1626,7 @@ flatpak_dir_pull (FlatpakDir *self,
g_autofree char *url = NULL;
g_autoptr(GBytes) summary_bytes = NULL;
g_autofree char *latest_rev = NULL;
+ g_autofree char *oci_uri = NULL;
g_auto(GLnxConsoleRef) console = { 0, };
g_autoptr(OstreeAsyncProgress) console_progress = NULL;
g_autoptr(GPtrArray) subdirs_arg = NULL;
@@ -1575,6 +1634,11 @@ flatpak_dir_pull (FlatpakDir *self,
if (!flatpak_dir_ensure_repo (self, cancellable, error))
return FALSE;
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, repository);
+ if (oci_uri != NULL)
+ return flatpak_dir_pull_oci (self, repository, ref, repo, flatpak_flags,
+ flags, progress, cancellable, error);
+
if (!ostree_repo_remote_get_url (self->repo,
repository,
&url,
@@ -2204,10 +2268,12 @@ char *
flatpak_dir_read_latest (FlatpakDir *self,
const char *remote,
const char *ref,
+ char **out_alt_id,
GCancellable *cancellable,
GError **error)
{
g_autofree char *remote_and_ref = NULL;
+ g_autofree char *alt_id = NULL;
char *res = NULL;
/* There may be several remotes with the same branch (if we for
@@ -2222,6 +2288,21 @@ flatpak_dir_read_latest (FlatpakDir *self,
if (!ostree_repo_resolve_rev (self->repo, remote_and_ref, FALSE, &res, error))
return NULL;
+ if (out_alt_id)
+ {
+ g_autoptr(GVariant) commit_data = NULL;
+ g_autoptr(GVariant) commit_metadata = NULL;
+ g_autofree char *tmp_dir_path = NULL;
+
+ if (!ostree_repo_load_commit (self->repo, res, &commit_data, NULL, error))
+ return FALSE;
+
+ commit_metadata = g_variant_get_child_value (commit_data, 0);
+ g_variant_lookup (commit_metadata, "xa.alt-id", "s", &alt_id);
+
+ *out_alt_id = g_steal_pointer (&alt_id);
+ }
+
return res;
}
@@ -3232,8 +3313,12 @@ flatpak_dir_deploy (FlatpakDir *self,
const char *checksum;
glnx_fd_close int checkoutdir_dfd = -1;
g_autoptr(GFile) tmp_dir_template = NULL;
+ g_autoptr(GVariant) commit_data = NULL;
g_autofree char *tmp_dir_path = NULL;
+ g_autofree char *alt_id = NULL;
gboolean created_extra_data = FALSE;
+ g_autoptr(GVariant) commit_metadata = NULL;
+ GVariantBuilder metadata_builder;
if (!flatpak_dir_ensure_repo (self, cancellable, error))
return FALSE;
@@ -3244,7 +3329,7 @@ flatpak_dir_deploy (FlatpakDir *self,
{
g_debug ("No checksum specified, getting tip of %s", ref);
- resolved_ref = flatpak_dir_read_latest (self, origin, ref, cancellable, error);
+ resolved_ref = flatpak_dir_read_latest (self, origin, ref, NULL, cancellable, error);
if (resolved_ref == NULL)
{
g_prefix_error (error, _("While trying to resolve ref %s: "), ref);
@@ -3265,6 +3350,12 @@ flatpak_dir_deploy (FlatpakDir *self,
return flatpak_fail (error, _("%s is not available"), ref);
}
+ if (!ostree_repo_load_commit (self->repo, checksum, &commit_data, NULL, error))
+ return FALSE;
+
+ commit_metadata = g_variant_get_child_value (commit_data, 0);
+ g_variant_lookup (commit_metadata, "xa.alt-id", "s", &alt_id);
+
real_checkoutdir = g_file_get_child (deploy_base, checksum);
if (g_file_query_exists (real_checkoutdir, cancellable))
{
@@ -3459,11 +3550,16 @@ flatpak_dir_deploy (FlatpakDir *self,
return FALSE;
}
+ g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}"));
+ if (alt_id)
+ g_variant_builder_add (&metadata_builder, "{s@v}", "alt-id",
+ g_variant_new_variant (g_variant_new_string (alt_id)));
+
deploy_data = flatpak_dir_new_deploy_data (origin,
checksum,
(char **) subpaths,
installed_size,
- NULL);
+ g_variant_builder_end (&metadata_builder));
deploy_data_file = g_file_get_child (checkoutdir, "deploy");
if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error))
@@ -3910,6 +4006,7 @@ flatpak_dir_install_bundle (FlatpakDir *self,
parts[1],
basename,
ref,
+ NULL, NULL,
gpg_data,
cancellable,
error);
@@ -4023,6 +4120,7 @@ flatpak_dir_update (FlatpakDir *self,
gboolean is_local;
g_autofree char *latest_rev = NULL;
const char *rev = NULL;
+ g_autofree char *oci_uri = NULL;
deploy_data = flatpak_dir_get_deploy_data (self, ref,
cancellable, NULL);
@@ -4039,7 +4137,9 @@ flatpak_dir_update (FlatpakDir *self,
if (!ostree_repo_remote_get_url (self->repo, remote_name, &url, error))
return FALSE;
- if (*url == 0)
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, remote_name);
+
+ if (*url == 0 && oci_uri == NULL)
return TRUE; /* Empty URL => disabled */
rev = checksum_or_latest;
@@ -4052,6 +4152,7 @@ flatpak_dir_update (FlatpakDir *self,
_g_strv_equal0 ((char **)subpaths, (char **)old_subpaths))
{
const char *installed_commit = flatpak_deploy_data_get_commit (deploy_data);
+ const char *installed_alt_id = flatpak_deploy_data_get_alt_id (deploy_data);
if (checksum_or_latest != NULL)
{
@@ -4074,7 +4175,8 @@ flatpak_dir_update (FlatpakDir *self,
ref,
&latest_rev))
{
- if (strcmp (latest_rev, installed_commit) == 0)
+ if (g_strcmp0 (latest_rev, installed_commit) == 0 ||
+ g_strcmp0 (latest_rev, installed_alt_id) == 0)
{
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
_("%s branch %s already installed"), ref, installed_commit);
@@ -4970,6 +5072,101 @@ flatpak_dir_cache_summary (FlatpakDir *self,
G_UNLOCK (cache);
}
+static gboolean
+flatpak_dir_remote_make_oci_summary (FlatpakDir *self,
+ const char *remote,
+ GBytes **out_summary,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakOciRegistry) registry = NULL;
+ g_autofree char *oci_ref = NULL;
+ g_autofree char *remote_oci_ref = NULL;
+ g_autofree char *oci_uri = NULL;
+ g_autofree char *oci_tag = NULL;
+ g_autofree char *oci_digest = NULL;
+ g_autoptr(GVariantBuilder) refs_builder = NULL;
+ g_autoptr(GVariantBuilder) additional_metadata_builder = NULL;
+ g_autoptr(GVariantBuilder) summary_builder = NULL;
+ g_autoptr(FlatpakOciManifest) manifest = NULL;
+ g_autoptr(GVariant) summary = NULL;
+ GHashTable *annotations;
+ g_autoptr(GVariantBuilder) ref_data_builder = NULL;
+
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, remote);
+ g_assert (oci_uri != NULL);
+
+ oci_tag = flatpak_dir_get_remote_oci_tag (self, remote);
+ if (oci_tag == NULL)
+ oci_tag = g_strdup ("latest");
+
+ oci_ref = flatpak_dir_get_remote_main_ref (self, remote);
+
+ registry = flatpak_oci_registry_new (oci_uri, FALSE, -1, NULL, error);
+ if (registry == NULL)
+ return FALSE;
+
+ manifest = flatpak_oci_registry_chose_image (registry, oci_tag, &oci_digest,
+ NULL, error);
+ if (manifest == NULL)
+ return FALSE;
+
+ annotations = flatpak_oci_manifest_get_annotations (manifest);
+ if (annotations)
+ flatpak_oci_parse_commit_annotations (annotations, NULL,
+ NULL, NULL,
+ &remote_oci_ref, NULL, NULL,
+ NULL);
+
+ refs_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(s(taya{sv}))"));
+ ref_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{s(tts)}"));
+ additional_metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+
+ if (remote_oci_ref != NULL && g_strcmp0 (remote_oci_ref, oci_ref) == 0 && g_str_has_prefix (oci_digest, "sha256:"))
+ {
+ const char *fake_commit = oci_digest + strlen ("sha256:");
+ guint64 installed_size = 0;
+ guint64 download_size = 0;
+ const char *installed_size_str;
+ const char *metadata_contents = NULL;
+ int i;
+
+ g_variant_builder_add_value (refs_builder,
+ g_variant_new ("(s(t@ay@a{sv}))", oci_ref,
+ 0,
+ ostree_checksum_to_bytes_v (fake_commit),
+ flatpak_gvariant_new_empty_string_dict ()));
+
+ for (i = 0; manifest->layers != NULL && manifest->layers[i] != NULL; i++)
+ download_size += manifest->layers[i]->size;
+
+ installed_size_str = g_hash_table_lookup (annotations, "org.flatpak.InstalledSize");
+ if (installed_size_str)
+ installed_size = g_ascii_strtoull (installed_size_str, NULL, 10);
+
+ metadata_contents = g_hash_table_lookup (annotations, "org.flatpak.Metadata");
+
+ g_variant_builder_add (ref_data_builder, "{s(tts)}",
+ oci_ref,
+ GUINT64_TO_BE (installed_size),
+ GUINT64_TO_BE (download_size),
+ metadata_contents ? metadata_contents : "");
+ }
+
+ g_variant_builder_add (additional_metadata_builder, "{sv}", "xa.cache",
+ g_variant_new_variant (g_variant_builder_end (ref_data_builder)));
+
+ summary_builder = g_variant_builder_new (OSTREE_SUMMARY_GVARIANT_FORMAT);
+
+ g_variant_builder_add_value (summary_builder, g_variant_builder_end (refs_builder));
+ g_variant_builder_add_value (summary_builder, g_variant_builder_end (additional_metadata_builder));
+
+ summary = g_variant_ref_sink (g_variant_builder_end (summary_builder));
+
+ *out_summary = g_variant_get_data_as_bytes (summary);
+ return TRUE;
+}
+
static gboolean
flatpak_dir_remote_fetch_summary (FlatpakDir *self,
const char *name,
@@ -4980,6 +5177,7 @@ flatpak_dir_remote_fetch_summary (FlatpakDir *self,
g_autofree char *url = NULL;
gboolean is_local;
g_autoptr(GError) local_error = NULL;
+ g_autofree char *oci_uri = NULL;
GBytes *summary;
if (!ostree_repo_remote_get_url (self->repo, name, &url, error))
@@ -5002,11 +5200,24 @@ flatpak_dir_remote_fetch_summary (FlatpakDir *self,
if (error == NULL)
error = &local_error;
- if (!ostree_repo_remote_fetch_summary (self->repo, name,
- &summary, NULL,
- cancellable,
- error))
- return FALSE;
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, name);
+
+ if (oci_uri != NULL)
+ {
+ if (!flatpak_dir_remote_make_oci_summary (self, name,
+ &summary,
+ cancellable,
+ error))
+ return FALSE;
+ }
+ else
+ {
+ if (!ostree_repo_remote_fetch_summary (self->repo, name,
+ &summary, NULL,
+ cancellable,
+ error))
+ return FALSE;
+ }
if (summary == NULL)
return flatpak_fail (error, "Remote listing for %s not available; server has no summary file\n" \
@@ -5501,6 +5712,45 @@ flatpak_dir_get_remote_title (FlatpakDir *self,
return NULL;
}
+char *
+flatpak_dir_get_remote_oci_uri (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ g_autofree char *group = get_group (remote_name);
+
+ if (config)
+ return g_key_file_get_string (config, group, "xa.oci-uri", NULL);
+
+ return NULL;
+}
+
+char *
+flatpak_dir_get_remote_main_ref (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ g_autofree char *group = get_group (remote_name);
+
+ if (config)
+ return g_key_file_get_string (config, group, "xa.main-ref", NULL);
+
+ return NULL;
+}
+
+char *
+flatpak_dir_get_remote_oci_tag (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ g_autofree char *group = get_group (remote_name);
+
+ if (config)
+ return g_key_file_get_string (config, group, "xa.oci-tag", NULL);
+
+ return NULL;
+}
+
char *
flatpak_dir_get_remote_default_branch (FlatpakDir *self,
const char *remote_name)
@@ -5560,13 +5810,18 @@ flatpak_dir_get_remote_disabled (FlatpakDir *self,
GKeyFile *config = ostree_repo_get_config (self->repo);
g_autofree char *group = get_group (remote_name);
g_autofree char *url = NULL;
+ g_autofree char *oci_uri = NULL;
if (config &&
g_key_file_get_boolean (config, group, "xa.disable", NULL))
return TRUE;
if (ostree_repo_remote_get_url (self->repo, remote_name, &url, NULL) && *url == 0)
- return TRUE; /* Empty URL => disabled */
+ {
+ oci_uri = flatpak_dir_get_remote_oci_uri (self, remote_name);
+ if (oci_uri == NULL)
+ return TRUE; /* Empty URL => disabled */
+ }
return FALSE;
}
@@ -5593,6 +5848,8 @@ create_origin_remote_config (OstreeRepo *repo,
const char *id,
const char *title,
const char *main_ref,
+ const char *oci_uri,
+ const char *oci_tag,
GKeyFile *new_config)
{
g_autofree char *remote = NULL;
@@ -5627,6 +5884,10 @@ create_origin_remote_config (OstreeRepo *repo,
g_key_file_set_string (new_config, group, "gpg-verify-summary", "true");
if (main_ref)
g_key_file_set_string (new_config, group, "xa.main-ref", main_ref);
+ if (oci_uri)
+ g_key_file_set_string (new_config, group, "xa.oci-uri", oci_uri);
+ if (oci_tag)
+ g_key_file_set_string (new_config, group, "xa.oci-tag", oci_tag);
return g_steal_pointer (&remote);
}
@@ -5637,6 +5898,8 @@ flatpak_dir_create_origin_remote (FlatpakDir *self,
const char *id,
const char *title,
const char *main_ref,
+ const char *oci_uri,
+ const char *oci_tag,
GBytes *gpg_data,
GCancellable *cancellable,
GError **error)
@@ -5644,7 +5907,7 @@ flatpak_dir_create_origin_remote (FlatpakDir *self,
g_autoptr(GKeyFile) new_config = g_key_file_new ();
g_autofree char *remote = NULL;
- remote = create_origin_remote_config (self->repo, url, id, title, main_ref, new_config);
+ remote = create_origin_remote_config (self->repo, url, id, title, main_ref, oci_uri, oci_tag, new_config);
if (!flatpak_dir_modify_remote (self, remote, new_config,
gpg_data, cancellable, error))
@@ -5856,7 +6119,7 @@ flatpak_dir_create_remote_for_ref_file (FlatpakDir *self,
if (remote == NULL)
{
- remote = flatpak_dir_create_origin_remote (self, url, name, title, ref,
+ remote = flatpak_dir_create_origin_remote (self, url, name, title, ref, NULL, NULL,
gpg_data, NULL, error);
if (remote == NULL)
return FALSE;
@@ -6633,6 +6896,7 @@ flatpak_dir_find_remote_related (FlatpakDir *self,
int i;
g_auto(GStrv) parts = NULL;
g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_related_free);
+ g_autofree char *url = NULL;
parts = flatpak_decompose_ref (ref, error);
if (parts == NULL)
@@ -6641,6 +6905,15 @@ flatpak_dir_find_remote_related (FlatpakDir *self,
if (!flatpak_dir_ensure_repo (self, cancellable, error))
return NULL;
+ if (!ostree_repo_remote_get_url (self->repo,
+ remote_name,
+ &url,
+ error))
+ return FALSE;
+
+ if (*url == 0)
+ return g_steal_pointer (&related); /* Empty url, silently disables updates */
+
if (!flatpak_dir_remote_fetch_summary (self, remote_name,
&summary_bytes,
cancellable, error))
diff --git a/common/flatpak-dir.h b/common/flatpak-dir.h
index 745f4ea4..61d7f380 100644
--- a/common/flatpak-dir.h
+++ b/common/flatpak-dir.h
@@ -130,6 +130,7 @@ const char * flatpak_deploy_data_get_origin (GVariant *deploy_data);
const char * flatpak_deploy_data_get_commit (GVariant *deploy_data);
const char ** flatpak_deploy_data_get_subpaths (GVariant *deploy_data);
guint64 flatpak_deploy_data_get_installed_size (GVariant *deploy_data);
+const char * flatpak_deploy_data_get_alt_id (GVariant *deploy_data);
GFile * flatpak_deploy_get_dir (FlatpakDeploy *deploy);
GFile * flatpak_deploy_get_files (FlatpakDeploy *deploy);
@@ -289,6 +290,7 @@ gboolean flatpak_dir_list_refs (FlatpakDir *self,
char * flatpak_dir_read_latest (FlatpakDir *self,
const char *remote,
const char *ref,
+ char **out_alt_id,
GCancellable *cancellable,
GError **error);
char * flatpak_dir_read_active (FlatpakDir *self,
@@ -426,6 +428,8 @@ char *flatpak_dir_create_origin_remote (FlatpakDir *self,
const char *id,
const char *title,
const char *main_ref,
+ const char *oci_uri,
+ const char *oci_tag,
GBytes *gpg_data,
GCancellable *cancellable,
GError **error);
@@ -462,6 +466,12 @@ gboolean flatpak_dir_remove_remote (FlatpakDir *self,
GError **error);
char *flatpak_dir_get_remote_title (FlatpakDir *self,
const char *remote_name);
+char *flatpak_dir_get_remote_main_ref (FlatpakDir *self,
+ const char *remote_name);
+char *flatpak_dir_get_remote_oci_uri (FlatpakDir *self,
+ const char *remote_name);
+char *flatpak_dir_get_remote_oci_tag (FlatpakDir *self,
+ const char *remote_name);
char *flatpak_dir_get_remote_default_branch (FlatpakDir *self,
const char *remote_name);
int flatpak_dir_get_remote_prio (FlatpakDir *self,
diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c
new file mode 100644
index 00000000..e9064b8c
--- /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 00000000..9a63deb0
--- /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 00000000..b4c5cec9
--- /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 00000000..9425cbe5
--- /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 00000000..0175f448
--- /dev/null
+++ b/common/flatpak-oci-registry.c
@@ -0,0 +1,1099 @@
+/*
+ * 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,
+ char **out_digest,
+ 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;
+ }
+
+ if (out_digest != NULL)
+ *out_digest = g_strdup (flatpak_oci_ref_get_digest (ref));
+
+ 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_checksum_reset (self->uncompressed_checksum);
+ g_checksum_reset (self->compressed_checksum);
+
+ if (self->archive)
+ {
+ archive_write_free (self->archive);
+ self->archive = NULL;
+ }
+
+ g_clear_object (&self->compressor);
+}
+
+
+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 00000000..13942546
--- /dev/null
+++ b/common/flatpak-oci-registry.h
@@ -0,0 +1,102 @@
+/*
+ * 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);
+
+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,
+ char **out_digest,
+ 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/common/flatpak-utils.c b/common/flatpak-utils.c
index e9b4055b..9f5cbefa 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -24,6 +24,7 @@
#include "lib/flatpak-error.h"
#include "flatpak-dir.h"
#include "flatpak-portal-error.h"
+#include "flatpak-oci-registry.h"
#include "flatpak-run.h"
#include
@@ -49,6 +50,18 @@ static const GDBusErrorEntry flatpak_error_entries[] = {
{FLATPAK_ERROR_NOT_INSTALLED, "org.freedesktop.Flatpak.Error.NotInstalled"},
};
+
+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
+propagate_libarchive_error (GError **error,
+ struct archive *a)
+{
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s", archive_error_string (a));
+}
+
GQuark
flatpak_error_quark (void)
{
@@ -1355,6 +1368,14 @@ flatpak_table_printer_add_column (FlatpakTablePrinter *printer,
g_ptr_array_add (printer->current, text ? g_strdup (text) : g_strdup (""));
}
+void
+flatpak_table_printer_add_column_len (FlatpakTablePrinter *printer,
+ const char *text,
+ gsize len)
+{
+ g_ptr_array_add (printer->current, text ? g_strndup (text, len) : g_strdup (""));
+}
+
void
flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer,
const char *text)
@@ -2159,6 +2180,14 @@ flatpak_open_in_tmpdir_at (int tmpdir_fd,
return TRUE;
}
+GVariant *
+flatpak_gvariant_new_empty_string_dict (void)
+{
+ g_auto(GVariantBuilder) builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+ return g_variant_builder_end (&builder);
+}
+
gboolean
flatpak_variant_save (GFile *dest,
GVariant *variant,
@@ -3981,6 +4010,116 @@ flatpak_pull_from_bundle (OstreeRepo *repo,
return TRUE;
}
+char *
+flatpak_pull_from_oci (OstreeRepo *repo,
+ FlatpakOciRegistry *registry,
+ const char *digest,
+ FlatpakOciManifest *manifest,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(OstreeMutableTree) archive_mtree = NULL;
+ g_autoptr(GFile) archive_root = NULL;
+ g_autofree char *commit_checksum = NULL;
+ g_autofree char *dir_uri = NULL;
+ const char *parent = NULL;
+ g_autofree char *subject = NULL;
+ g_autofree char *body = NULL;
+ guint64 timestamp = 0;
+ g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ GHashTable *annotations;
+ int i;
+
+ g_assert (ref != NULL);
+ g_assert (g_str_has_prefix (digest, "sha256:"));
+
+ annotations = flatpak_oci_manifest_get_annotations (manifest);
+ if (annotations)
+ flatpak_oci_parse_commit_annotations (annotations, ×tamp,
+ &subject, &body,
+ NULL, NULL, NULL,
+ metadata_builder);
+
+ g_variant_builder_add (metadata_builder, "{s@v}", "xa.alt-id",
+ g_variant_new_variant (g_variant_new_string (digest + strlen("sha256:"))));
+
+ if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+ return NULL;
+
+ /* There is no way to write a subset of the archive to a mtree, so instead
+ we write all of it and then build a new mtree with the subset */
+ archive_mtree = ostree_mutable_tree_new ();
+
+ for (i = 0; manifest->layers[i] != NULL; i++)
+ {
+ FlatpakOciDescriptor *layer = manifest->layers[i];
+ OstreeRepoImportArchiveOptions opts = { 0, };
+ free_read_archive struct archive *a = NULL;
+ glnx_fd_close int layer_fd = -1;
+
+ opts.autocreate_parents = TRUE;
+ opts.ignore_unsupported_content = TRUE;
+
+ layer_fd = flatpak_oci_registry_download_blob (registry, layer->digest,
+ NULL, NULL,
+ cancellable, error);
+ if (layer_fd == -1)
+ goto error;
+
+ a = archive_read_new ();
+#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
+ archive_read_support_filter_all (a);
+#else
+ archive_read_support_compression_all (a);
+#endif
+ archive_read_support_format_all (a);
+ if (archive_read_open_fd (a, layer_fd, 8192) != ARCHIVE_OK)
+ {
+ propagate_libarchive_error (error, a);
+ goto error;
+ }
+
+ if (!ostree_repo_import_archive_to_mtree (repo, &opts, a, archive_mtree, NULL, cancellable, error))
+ goto error;
+
+ if (archive_read_close (a) != ARCHIVE_OK)
+ {
+ propagate_libarchive_error (error, a);
+ goto error;
+ }
+ }
+
+ if (!ostree_repo_write_mtree (repo, archive_mtree, &archive_root, cancellable, error))
+ goto error;
+
+ if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile *) archive_root, error))
+ goto error;
+
+ if (!ostree_repo_write_commit_with_time (repo,
+ parent,
+ subject,
+ body,
+ g_variant_builder_end (metadata_builder),
+ OSTREE_REPO_FILE (archive_root),
+ timestamp,
+ &commit_checksum,
+ cancellable, error))
+ goto error;
+
+ ostree_repo_transaction_set_ref (repo, NULL, ref, commit_checksum);
+
+ if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+ return NULL;
+
+ return g_steal_pointer (&commit_checksum);
+
+ error:
+
+ ostree_repo_abort_transaction (repo, cancellable, NULL);
+ return NULL;
+}
+
/* This allocates and locks a subdir of the tmp dir, using an existing
* one with the same prefix if it is not in use already. */
gboolean
@@ -4192,6 +4331,8 @@ flatpak_number_prompt (int min, int max, const char *prompt, ...)
typedef struct {
GMainLoop *loop;
GError *error;
+ GOutputStream *out;
+ guint64 downloaded_bytes;
GString *content;
char buffer[16*1024];
FlatpakLoadUriProgress progress;
@@ -4222,18 +4363,40 @@ load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data)
nread = g_input_stream_read_finish (stream, res, &data->error);
if (nread == -1 || nread == 0)
{
- data->progress (data->content->len, data->user_data);
+ if (data->progress)
+ data->progress (data->downloaded_bytes, data->user_data);
g_input_stream_close_async (stream,
G_PRIORITY_DEFAULT, NULL,
stream_closed, data);
return;
}
- g_string_append_len (data->content, data->buffer, nread);
+ if (data->out != NULL)
+ {
+ gsize n_written;
+
+ if (!g_output_stream_write_all (data->out, data->buffer, nread, &n_written,
+ NULL, &data->error))
+ {
+ data->downloaded_bytes += n_written;
+ g_input_stream_close_async (stream,
+ G_PRIORITY_DEFAULT, NULL,
+ stream_closed, data);
+ return;
+ }
+
+ data->downloaded_bytes += n_written;
+ }
+ else
+ {
+ data->downloaded_bytes += nread;
+ g_string_append_len (data->content, data->buffer, nread);
+ }
if (g_get_monotonic_time () - data->last_progress_time > 1 * G_USEC_PER_SEC)
{
- data->progress (data->content->len, data->user_data);
+ if (data->progress)
+ data->progress (data->downloaded_bytes, data->user_data);
data->last_progress_time = g_get_monotonic_time ();
}
@@ -4248,7 +4411,6 @@ load_uri_callback (GObject *source_object,
gpointer user_data)
{
SoupRequestHTTP *request = SOUP_REQUEST_HTTP(source_object);
- g_autoptr(GError) error = NULL;
g_autoptr(GInputStream) in = NULL;
LoadUriData *data = user_data;
@@ -4287,6 +4449,32 @@ load_uri_callback (GObject *source_object,
load_uri_read_cb, data);
}
+SoupSession *
+flatpak_create_soup_session (const char *user_agent)
+{
+ SoupSession *soup_session;
+ const char *http_proxy;
+
+ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent,
+ SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ SOUP_SESSION_TIMEOUT, 60,
+ SOUP_SESSION_IDLE_TIMEOUT, 60,
+ NULL);
+ soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER);
+ http_proxy = g_getenv ("http_proxy");
+ if (http_proxy)
+ {
+ g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy);
+ if (!proxy_uri)
+ g_warning ("Invalid proxy URI '%s'", http_proxy);
+ else
+ g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+ }
+
+ return soup_session;
+}
+
GBytes *
flatpak_load_http_uri (SoupSession *soup_session,
const char *uri,
@@ -4296,7 +4484,6 @@ flatpak_load_http_uri (SoupSession *soup_session,
GError **error)
{
GBytes *bytes = NULL;
- g_autoptr(SoupMessage) msg = NULL;
g_autoptr(SoupRequestHTTP) request = NULL;
g_autoptr(GMainLoop) loop = NULL;
g_autoptr(GString) content = g_string_new ("");
@@ -4329,13 +4516,54 @@ flatpak_load_http_uri (SoupSession *soup_session,
}
bytes = g_string_free_to_bytes (g_steal_pointer (&content));
- g_debug ("Received %" G_GSIZE_FORMAT " bytes", g_bytes_get_size (bytes));
+ g_debug ("Received %" G_GSIZE_FORMAT " bytes", data.downloaded_bytes);
return bytes;
}
+gboolean
+flatpak_download_http_uri (SoupSession *soup_session,
+ const char *uri,
+ GOutputStream *out,
+ FlatpakLoadUriProgress progress,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(SoupRequestHTTP) request = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ LoadUriData data = { NULL };
+ g_debug ("Loading %s using libsoup", uri);
+ loop = g_main_loop_new (NULL, TRUE);
+ data.loop = loop;
+ data.out = out;
+ data.progress = progress;
+ data.user_data = user_data;
+ data.last_progress_time = g_get_monotonic_time ();
+
+ request = soup_session_request_http (soup_session, "GET",
+ uri, error);
+ if (request == NULL)
+ return FALSE;
+
+ soup_request_send_async (SOUP_REQUEST(request),
+ cancellable,
+ load_uri_callback, &data);
+
+ g_main_loop_run (loop);
+
+ if (data.error)
+ {
+ g_propagate_error (error, data.error);
+ return FALSE;
+ }
+
+ g_debug ("Received %" G_GSIZE_FORMAT " bytes", data.downloaded_bytes);
+
+ return TRUE;
+}
/* Uncomment to get debug traces in /tmp/flatpak-completion-debug.txt (nice
* to not have it interfere with stdout/stderr)
diff --git a/common/flatpak-utils.h b/common/flatpak-utils.h
index 9c7105fc..1a11883e 100644
--- a/common/flatpak-utils.h
+++ b/common/flatpak-utils.h
@@ -37,6 +37,13 @@ typedef enum {
} FlatpakHostCommandFlags;
+/* https://bugzilla.gnome.org/show_bug.cgi?id=766370 */
+#if !GLIB_CHECK_VERSION(2, 49, 3)
+#define FLATPAK_VARIANT_BUILDER_INITIALIZER {{0,}}
+#else
+#define FLATPAK_VARIANT_BUILDER_INITIALIZER {{{0,}}}
+#endif
+
gboolean flatpak_fail (GError **error,
const char *format,
...);
@@ -69,6 +76,7 @@ gboolean flatpak_variant_save (GFile *dest,
GVariant *variant,
GCancellable *cancellable,
GError **error);
+GVariant * flatpak_gvariant_new_empty_string_dict (void);
void flatpak_variant_builder_init_from_variant (GVariantBuilder *builder,
const char *type,
GVariant *variant);
@@ -238,6 +246,9 @@ FlatpakTablePrinter *flatpak_table_printer_new (void);
void flatpak_table_printer_free (FlatpakTablePrinter *printer);
void flatpak_table_printer_add_column (FlatpakTablePrinter *printer,
const char *text);
+void flatpak_table_printer_add_column_len (FlatpakTablePrinter *printer,
+ const char *text,
+ gsize len);
void flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer,
const char *text);
void flatpak_table_printer_finish_row (FlatpakTablePrinter *printer);
@@ -294,6 +305,13 @@ gboolean flatpak_pull_from_bundle (OstreeRepo *repo,
GCancellable *cancellable,
GError **error);
+char * flatpak_pull_from_oci (OstreeRepo *repo,
+ FlatpakOciRegistry *registry,
+ const char *digest,
+ FlatpakOciManifest *manifest,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error);
typedef struct
{
@@ -520,12 +538,20 @@ long flatpak_number_prompt (int min, int max, const char *prompt, ...);
typedef void (*FlatpakLoadUriProgress) (guint64 downloaded_bytes,
gpointer user_data);
+SoupSession * flatpak_create_soup_session (const char *user_agent);
GBytes * flatpak_load_http_uri (SoupSession *soup_session,
const char *uri,
FlatpakLoadUriProgress progress,
gpointer user_data,
GCancellable *cancellable,
GError **error);
+gboolean flatpak_download_http_uri (SoupSession *soup_session,
+ const char *uri,
+ GOutputStream *out,
+ FlatpakLoadUriProgress progress,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error);
typedef struct {
char *shell_cur;
diff --git a/doc/flatpak-install.xml b/doc/flatpak-install.xml
index 949da4ee..10797e39 100644
--- a/doc/flatpak-install.xml
+++ b/doc/flatpak-install.xml
@@ -44,6 +44,16 @@
FILENAME
+
+ flatpak install --oci
+ OPTION
+
+ --bundle
+ --from
+
+ REGISTRYURI
+ TAG
+
@@ -120,6 +130,14 @@
+
+
+
+
+ Install from a oci registry with a given uri and optionally a tag (defaults to latest).
+
+
+
diff --git a/lib/Makefile.am.inc b/lib/Makefile.am.inc
index 9e3f81d1..e1db0cf1 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 = \
diff --git a/lib/flatpak-installation.c b/lib/flatpak-installation.c
index 14da5be0..cd7272e0 100644
--- a/lib/flatpak-installation.c
+++ b/lib/flatpak-installation.c
@@ -372,6 +372,8 @@ get_ref (FlatpakDir *dir,
g_auto(GStrv) parts = NULL;
const char *origin = NULL;
const char *commit = NULL;
+ const char *alt_id = NULL;
+ g_autofree char *latest_alt_id = NULL;
g_autoptr(GFile) deploy_dir = NULL;
g_autoptr(GFile) deploy_subdir = NULL;
g_autofree char *deploy_path = NULL;
@@ -388,6 +390,7 @@ get_ref (FlatpakDir *dir,
return NULL;
origin = flatpak_deploy_data_get_origin (deploy_data);
commit = flatpak_deploy_data_get_commit (deploy_data);
+ alt_id = flatpak_deploy_data_get_alt_id (deploy_data);
subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
installed_size = flatpak_deploy_data_get_installed_size (deploy_data);
@@ -403,11 +406,11 @@ get_ref (FlatpakDir *dir,
is_current = TRUE;
}
- latest_commit = flatpak_dir_read_latest (dir, origin, full_ref, NULL, NULL);
+ latest_commit = flatpak_dir_read_latest (dir, origin, full_ref, &latest_alt_id, NULL, NULL);
return flatpak_installed_ref_new (full_ref,
- commit,
- latest_commit,
+ alt_id ? alt_id : commit,
+ latest_alt_id ? latest_alt_id : latest_commit,
origin, subpaths,
deploy_path,
installed_size,
diff --git a/tests/Makefile.am.inc b/tests/Makefile.am.inc
index c0645d65..d1b4a43e 100644
--- a/tests/Makefile.am.inc
+++ b/tests/Makefile.am.inc
@@ -117,6 +117,7 @@ dist_test_scripts = \
tests/test-bundle-system.sh \
tests/test-builder.sh \
tests/test-builder-python.sh \
+ tests/test-oci.sh \
$(NULL)
test_programs = testdb test-doc-portal testlibrary
diff --git a/tests/test-oci.sh b/tests/test-oci.sh
new file mode 100755
index 00000000..b3088a23
--- /dev/null
+++ b/tests/test-oci.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+skip_without_bwrap
+skip_without_user_xattrs
+
+echo "1..4"
+
+setup_repo
+
+${FLATPAK} ${U} install test-repo org.test.Platform master
+
+${FLATPAK} build-bundle --oci repo oci-dir org.test.Hello
+
+assert_has_file oci-dir/oci-layout
+assert_has_dir oci-dir/blobs/sha256
+assert_has_dir oci-dir/refs
+assert_file_has_content oci-dir/refs/latest "application/vnd.oci.image.manifest.v1+json"
+
+for i in oci-dir/blobs/sha256/*; do
+ echo $(basename $i) $i >> sums
+done
+sha256sum -c sums
+
+echo "ok export oci"
+
+ostree --repo=repo2 init --mode=archive-z2
+
+$FLATPAK build-import-bundle --oci repo2 oci-dir
+
+ostree checkout -U --repo=repo2 app/org.test.Hello/$ARCH/master checked-out
+
+assert_has_dir checked-out/files
+assert_has_file checked-out/files/bin/hello.sh
+assert_has_file checked-out/metadata
+
+echo "ok commit oci"
+
+${FLATPAK} install ${U} --oci oci-dir latest
+
+run org.test.Hello > hello_out
+assert_file_has_content hello_out '^Hello world, from a sandbox$'
+
+echo "ok install oci"
+
+make_updated_app
+${FLATPAK} build-bundle --oci repo oci-dir org.test.Hello
+
+${FLATPAK} update ${U} org.test.Hello
+run org.test.Hello > hello_out
+assert_file_has_content hello_out '^Hello world, from a sandboxUPDATED$'
+
+echo "ok update oci"