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"