/* * Copyright © 2015 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 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 #include "libgsystem.h" #include "libglnx/libglnx.h" #include "flatpak-builtins.h" #include "flatpak-utils.h" static char *opt_ref; static gboolean opt_oci = FALSE; static GOptionEntry options[] = { { "ref", 0, 0, G_OPTION_ARG_STRING, &opt_ref, "Override the ref used for the imported bundle", "REF" }, { "oci", 0, 0, G_OPTION_ARG_NONE, &opt_oci, "Import oci image instead of flatpak bundle"}, { NULL } }; static gboolean import_oci (OstreeRepo *repo, GFile *file, GCancellable *cancellable, GError **error) { #if !defined(HAVE_OSTREE_EXPORT_PATH_PREFIX) /* This code actually doesn't user path_prefix, but it need the support for reading commits from the transaction that was added at the same time. */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "This version of ostree is to old to support OCI exports"); return FALSE; #elif !defined(HAVE_LIBARCHIVE) g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "This version of flatpak is not compiled with libarchive support"); return FALSE; #else g_autoptr(OstreeMutableTree) archive_mtree = NULL; g_autoptr(OstreeMutableTree) mtree = NULL; g_autoptr(OstreeMutableTree) files_mtree = NULL; g_autoptr(OstreeMutableTree) export_mtree = NULL; g_autoptr(GFile) archive_root = NULL; g_autoptr(GFile) root = NULL; g_autoptr(GFile) files = NULL; g_autoptr(GFile) export = NULL; g_autoptr(GFile) ref = NULL; g_autoptr(GFile) commit = NULL; g_autoptr(GFile) commitmeta = NULL; g_autoptr(GFile) metadata = NULL; g_autofree char *commit_checksum = NULL; g_autofree char *ref_data = NULL; g_autofree char *commit_data = NULL; gsize commit_size; g_autofree char *commitmeta_data = NULL; g_autofree char *parent = NULL; const char *subject; const char *body; const char *target_ref; const char *files_source; gsize commitmeta_size; g_autoptr(GVariant) commitv = NULL; g_autoptr(GVariant) commitv_metadata = NULL; g_autoptr(GVariant) commitmetav = NULL; if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; /* 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 (); if (!ostree_repo_write_archive_to_mtree (repo, file, archive_mtree, NULL, TRUE, cancellable, error)) return FALSE; if (!ostree_repo_write_mtree (repo, archive_mtree, &archive_root, cancellable, error)) return FALSE; if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile *) archive_root, error)) return FALSE; ref = g_file_resolve_relative_path (archive_root, "rootfs/ref"); metadata = g_file_resolve_relative_path (archive_root, "rootfs/metadata"); commit = g_file_resolve_relative_path (archive_root, "rootfs/commit"); commitmeta = g_file_resolve_relative_path (archive_root, "rootfs/commitmeta"); if (!g_file_query_exists (ref, NULL)) return flatpak_fail (error, "Required file ref not in tarfile"); if (!g_file_query_exists (metadata, NULL)) return flatpak_fail (error, "Required file metadata not in tarfile"); if (!g_file_query_exists (commit, NULL)) return flatpak_fail (error, "Required file commit not in tarfile"); if (!g_file_load_contents (ref, cancellable, &ref_data, NULL, NULL, error)) return FALSE; if (g_str_has_prefix (ref_data, "app/")) files_source = "rootfs/app"; else files_source = "rootfs/usr"; files = g_file_resolve_relative_path (archive_root, files_source); if (!g_file_query_exists (files, NULL)) return flatpak_fail (error, "Required directory %s not in tarfile", files_source); export = g_file_resolve_relative_path (archive_root, "rootfs/export"); if (!g_file_load_contents (commit, cancellable, &commit_data, &commit_size, NULL, error)) return FALSE; commitv = g_variant_new_from_data (OSTREE_COMMIT_GVARIANT_FORMAT, g_steal_pointer (&commit_data), commit_size, FALSE, g_free, commit_data); if (!ostree_validate_structureof_commit (commitv, error)) return FALSE; if (g_file_query_exists (commitmeta, NULL) && !g_file_load_contents (commitmeta, cancellable, &commitmeta_data, &commitmeta_size, NULL, error)) return FALSE; if (commitmeta_data != NULL) commitmetav = g_variant_new_from_data (G_VARIANT_TYPE ("a{sv}"), g_steal_pointer (&commitmeta_data), commitmeta_size, FALSE, g_free, commitmeta_data); mtree = ostree_mutable_tree_new (); if (!flatpak_mtree_create_root (repo, mtree, cancellable, error)) return FALSE; if (!ostree_mutable_tree_ensure_dir (mtree, "files", &files_mtree, error)) return FALSE; if (!ostree_repo_write_directory_to_mtree (repo, files, files_mtree, NULL, cancellable, error)) return FALSE; if (g_file_query_exists (export, NULL)) { if (!ostree_mutable_tree_ensure_dir (mtree, "export", &export_mtree, error)) return FALSE; if (!ostree_repo_write_directory_to_mtree (repo, export, export_mtree, NULL, cancellable, error)) return FALSE; } if (!ostree_mutable_tree_replace_file (mtree, "metadata", ostree_repo_file_get_checksum ((OstreeRepoFile *) metadata), error)) return FALSE; if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) return FALSE; /* Verify that we created the same contents */ { g_autoptr(GVariant) tree_contents_bytes = NULL; g_autofree char *tree_contents_checksum = NULL; g_autoptr(GVariant) tree_metadata_bytes = NULL; g_autofree char *tree_metadata_checksum = NULL; tree_contents_bytes = g_variant_get_child_value (commitv, 6); tree_contents_checksum = ostree_checksum_from_bytes_v (tree_contents_bytes); tree_metadata_bytes = g_variant_get_child_value (commitv, 7); tree_metadata_checksum = ostree_checksum_from_bytes_v (tree_metadata_bytes); if (strcmp (tree_contents_checksum, ostree_repo_file_tree_get_contents_checksum ((OstreeRepoFile *) root))) return flatpak_fail (error, "Imported content checksum (%s) does not match original checksum (%s)", tree_contents_checksum, ostree_repo_file_tree_get_contents_checksum ((OstreeRepoFile *) root)); if (strcmp (tree_metadata_checksum, ostree_repo_file_tree_get_metadata_checksum ((OstreeRepoFile *) root))) return flatpak_fail (error, "Imported metadata checksum (%s) does not match original checksum (%s)", tree_metadata_checksum, ostree_repo_file_tree_get_metadata_checksum ((OstreeRepoFile *) root)); } commitv_metadata = g_variant_get_child_value (commitv, 0); parent = ostree_commit_get_parent (commitv); g_variant_get_child (commitv, 3, "s", &subject); g_variant_get_child (commitv, 4, "s", &body); if (!ostree_repo_write_commit_with_time (repo, parent, subject, body, commitv_metadata, OSTREE_REPO_FILE (root), ostree_commit_get_timestamp (commitv), &commit_checksum, cancellable, error)) return FALSE; if (commitmetav != NULL && !ostree_repo_write_commit_detached_metadata (repo, commit_checksum, commitmetav, cancellable, error)) return FALSE; if (opt_ref != NULL) target_ref = opt_ref; else target_ref = ref_data; ostree_repo_transaction_set_ref (repo, NULL, target_ref, commit_checksum); if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) return FALSE; g_print ("Importing %s (%s)\n", target_ref, commit_checksum); return TRUE; #endif } static gboolean import_bundle (OstreeRepo *repo, GFile *file, GCancellable *cancellable, GError **error) { g_autoptr(GVariant) metadata = NULL; g_autofree char *bundle_ref = NULL; g_autofree char *to_checksum = NULL; const char *ref; metadata = flatpak_bundle_load (file, &to_checksum, &bundle_ref, NULL, NULL, NULL, error); if (metadata == NULL) return FALSE; if (opt_ref != NULL) ref = opt_ref; else ref = bundle_ref; g_print ("Importing %s (%s)\n", ref, to_checksum); if (!flatpak_pull_from_bundle (repo, file, NULL, ref, FALSE, cancellable, error)) return FALSE; return TRUE; } gboolean flatpak_builtin_build_import (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) repofile = NULL; g_autoptr(OstreeRepo) repo = NULL; const char *location; const char *filename; context = g_option_context_new ("LOCATION FILENAME - Import a file bundle into a local repository"); if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) return FALSE; if (argc < 3) return usage_error (context, "LOCATION and FILENAME must be specified", error); location = argv[1]; filename = argv[2]; repofile = g_file_new_for_commandline_arg (location); repo = ostree_repo_new (repofile); if (!g_file_query_exists (repofile, cancellable)) return flatpak_fail (error, "'%s' is not a valid repository", location); file = g_file_new_for_commandline_arg (filename); if (!ostree_repo_open (repo, cancellable, error)) return FALSE; if (opt_oci) { if (!import_oci (repo, file, cancellable, error)) return FALSE; } else { if (!import_bundle (repo, file, cancellable, error)) return FALSE; } return TRUE; }