diff --git a/app/xdg-app-builtins-build-bundle.c b/app/xdg-app-builtins-build-bundle.c index b56a3230..57d30395 100644 --- a/app/xdg-app-builtins-build-bundle.c +++ b/app/xdg-app-builtins-build-bundle.c @@ -34,16 +34,23 @@ #include "xdg-app-utils.h" #include "xdg-app-chain-input-stream.h" +#ifdef HAVE_LIBARCHIVE +#include +#include +#endif + static char *opt_arch; static char *opt_repo_url; static gboolean opt_runtime = FALSE; static char **opt_gpg_file; +static gboolean opt_oci = FALSE; static GOptionEntry options[] = { { "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, "Export runtime instead of app"}, { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, "Arch to bundle for", "ARCH" }, { "repo-url", 0, 0, G_OPTION_ARG_STRING, &opt_repo_url, "Url for repo", "URL" }, { "gpg-keys", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_gpg_file, "Add GPG key from FILE (- for stdin)", "FILE" }, + { "oci", 0, 0, G_OPTION_ARG_NONE, &opt_oci, "Export oci image instead of xdg-app bundle"}, { NULL } }; @@ -244,6 +251,623 @@ build_bundle (OstreeRepo *repo, GFile *file, return TRUE; } +#if defined(HAVE_LIBARCHIVE) && defined(HAVE_OSTREE_EXPORT_PATH_PREFIX) + +GLNX_DEFINE_CLEANUP_FUNCTION(void*, xdg_app_local_free_read_archive, archive_read_free) +#define free_read_archive __attribute__ ((cleanup(xdg_app_local_free_read_archive))) + +GLNX_DEFINE_CLEANUP_FUNCTION(void*, xdg_app_local_free_write_archive, archive_write_free) +#define free_write_archive __attribute__ ((cleanup(xdg_app_local_free_write_archive))) + +GLNX_DEFINE_CLEANUP_FUNCTION(void*, xdg_app_local_free_archive_entry, archive_entry_free) +#define free_archive_entry __attribute__ ((cleanup(xdg_app_local_free_archive_entry))) + + +typedef struct { + GString *str; + int depth; + GList *index; +} JsonWriter; + +static void +json_writer_init (JsonWriter *writer) +{ + memset (writer, 0, sizeof (*writer)); + writer->str = g_string_new (""); + writer->index = g_list_prepend (writer->index, 0); +} + +static void +json_writer_indent (JsonWriter *writer) +{ + int i; + for (i = 0; i < writer->depth; i++) + g_string_append (writer->str, " "); +} + +static void +json_writer_add_bool (JsonWriter *writer, gboolean val) +{ + if (val) + g_string_append (writer->str, "true"); + else + g_string_append (writer->str, "false"); +} + +static void +json_writer_add_string (JsonWriter *writer, const gchar *str) +{ + const gchar *p; + + g_string_append_c (writer->str, '"'); + + for (p = str; *p != 0; p++) + { + if (*p == '\\' || *p == '"') + { + g_string_append_c (writer->str, '\\'); + g_string_append_c (writer->str, *p); + } + else if ((*p > 0 && *p < 0x1f) || *p == 0x7f) + { + switch (*p) + { + case '\b': + g_string_append (writer->str, "\\b"); + break; + case '\f': + g_string_append (writer->str, "\\f"); + break; + case '\n': + g_string_append (writer->str, "\\n"); + break; + case '\r': + g_string_append (writer->str, "\\r"); + break; + case '\t': + g_string_append (writer->str, "\\t"); + break; + default: + g_string_append_printf (writer->str, "\\u00%02x", (guint)*p); + break; + } + } + else + { + g_string_append_c (writer->str, *p); + } + } + + g_string_append_c (writer->str, '"'); +} + +static void +json_writer_start_item (JsonWriter *writer) +{ + int index = GPOINTER_TO_INT(writer->index->data); + if (index != 0) + g_string_append (writer->str, ",\n"); + else + g_string_append (writer->str, "\n"); + json_writer_indent (writer); + writer->index->data = GINT_TO_POINTER(index+1); +} + +static void +json_writer_open_scope (JsonWriter *writer) +{ + writer->depth += 1; + writer->index = g_list_prepend (writer->index, 0); +} + +static void +json_writer_close_scope (JsonWriter *writer) +{ + GList *l; + writer->depth -= 1; + l = writer->index; + writer->index = g_list_remove_link (writer->index, l); + g_list_free (l); + g_string_append (writer->str, "\n"); + json_writer_indent (writer); +} + +static void +json_writer_open_struct (JsonWriter *writer) +{ + g_string_append (writer->str, "{"); + json_writer_open_scope (writer); +} + +static void +json_writer_close_struct (JsonWriter *writer) +{ + int index; + json_writer_close_scope (writer); + g_string_append (writer->str, "}"); + + /* Last newline in file */ + index = GPOINTER_TO_INT(writer->index->data); + if (index == 0) + g_string_append (writer->str, "\n"); +} + +static void +json_writer_open_array (JsonWriter *writer) +{ + g_string_append (writer->str, "["); + json_writer_open_scope (writer); +} + +static void +json_writer_close_array (JsonWriter *writer) +{ + json_writer_close_scope (writer); + g_string_append (writer->str, "]"); +} + +static void +json_writer_add_property (JsonWriter *writer, const gchar *name) +{ + json_writer_start_item (writer); + json_writer_add_string (writer, name); + g_string_append (writer->str, ": "); +} + +static void +json_writer_add_struct_property (JsonWriter *writer, const gchar *name) +{ + json_writer_add_property (writer, name); + json_writer_open_struct (writer); +} + +static void +json_writer_add_array_property (JsonWriter *writer, const gchar *name) +{ + json_writer_add_property (writer, name); + json_writer_open_array (writer); +} + +static void +json_writer_add_string_property (JsonWriter *writer, const gchar *name, const char *value) +{ + json_writer_add_property (writer, name); + json_writer_add_string (writer, value); +} + +static void +json_writer_add_bool_property (JsonWriter *writer, const gchar *name, gboolean value) +{ + json_writer_add_property (writer, name); + json_writer_add_bool (writer, value); +} + +static void +json_writer_add_array_item (JsonWriter *writer, const gchar *string) +{ + json_writer_start_item (writer); + json_writer_add_string (writer, string); +} + +static void +json_writer_add_array_struct (JsonWriter *writer) +{ + json_writer_start_item (writer); + json_writer_open_struct (writer); +} + +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; +} + +struct archive_entry * +new_entry (struct archive *a, + const char *name, + OstreeRepoExportArchiveOptions *opts) +{ + struct archive_entry *entry = archive_entry_new2 (a); + time_t ts = (time_t) opts->timestamp_secs; + + archive_entry_update_pathname_utf8 (entry, name); + archive_entry_set_ctime (entry, ts, 0); + archive_entry_set_mtime (entry, ts, 0); + archive_entry_set_atime (entry, ts, 0); + archive_entry_set_uid (entry, 0); + archive_entry_set_gid (entry, 0); + + return entry; +} + + +static gboolean +add_dir (struct archive *a, + const char *name, + OstreeRepoExportArchiveOptions *opts, + GError **error) +{ + g_autofree char *full_name = g_build_filename ("rootfs", name, NULL); + free_archive_entry struct archive_entry *entry = new_entry (a, full_name, opts); + + archive_entry_set_mode (entry, AE_IFDIR | 0755); + + if (archive_write_header (a, entry) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + return TRUE; +} + +static gboolean +add_symlink (struct archive *a, + const char *name, + const char *target, + OstreeRepoExportArchiveOptions *opts, + GError **error) +{ + g_autofree char *full_name = g_build_filename ("rootfs", name, NULL); + free_archive_entry struct archive_entry *entry = new_entry (a, full_name, opts); + + archive_entry_set_mode (entry, AE_IFLNK | 0755); + archive_entry_set_symlink (entry, target); + + if (archive_write_header (a, entry) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + return TRUE; +} + +static gboolean +add_file (struct archive *a, + const char *name, + OstreeRepo *repo, + GFile *file, + OstreeRepoExportArchiveOptions *opts, + GCancellable *cancellable, + GError **error) +{ + free_archive_entry struct archive_entry *entry = new_entry (a, name, opts); + guint8 buf[8192]; + g_autoptr(GInputStream) file_in = NULL; + g_autoptr(GFileInfo) file_info = NULL; + const char *checksum; + + checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)file); + + if (!ostree_repo_load_file (repo, checksum, &file_in, &file_info, NULL, + cancellable, error)) + return FALSE; + + archive_entry_set_uid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::uid")); + archive_entry_set_gid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::gid")); + archive_entry_set_mode (entry, g_file_info_get_attribute_uint32 (file_info, "unix::mode")); + archive_entry_set_size (entry, g_file_info_get_size (file_info)); + + if (archive_write_header (a, entry) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + while (TRUE) + { + ssize_t r; + gssize bytes_read = g_input_stream_read (file_in, buf, sizeof (buf), + cancellable, error); + if (bytes_read < 0) + return FALSE;; + if (bytes_read == 0) + break; + + r = archive_write_data (a, buf, bytes_read); + if (r != bytes_read) + return propagate_libarchive_error (error, a); + } + + if (archive_write_finish_entry (a) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + return TRUE; +} + +static gboolean +add_file_from_data (struct archive *a, + const char *name, + OstreeRepo *repo, + const char *data, + gsize size, + OstreeRepoExportArchiveOptions *opts, + GCancellable *cancellable, + GError **error) +{ + free_archive_entry struct archive_entry *entry = new_entry (a, name, opts); + ssize_t r; + + archive_entry_set_mode (entry, AE_IFREG | 0755); + archive_entry_set_size (entry, size); + + if (archive_write_header (a, entry) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + r = archive_write_data (a, data, size); + if (r != size) + return propagate_libarchive_error (error, a); + + if (archive_write_finish_entry (a) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + return TRUE; +} + +static const char * +get_oci_arch (const char *arch) +{ + if (strcmp (arch, "x86_64") == 0) + return "amd64"; + if (strcmp (arch, "aarch64") == 0) + return "arm64"; + return arch; +} + +static GString * +generate_config_json (const char *arch) +{ + JsonWriter writer; + + json_writer_init (&writer); + + json_writer_open_struct (&writer); + json_writer_add_string_property (&writer, "ociVersion", "0.5.0"); + json_writer_add_struct_property (&writer, "platform"); + { + json_writer_add_string_property (&writer, "os", "linux"); + json_writer_add_string_property (&writer, "arch", get_oci_arch (arch)); + json_writer_close_struct (&writer); + } + json_writer_add_struct_property (&writer, "process"); + { + json_writer_add_bool_property (&writer, "terminal", TRUE); + json_writer_add_array_property (&writer, "args"); + json_writer_add_array_item (&writer, "sh"); + json_writer_close_array (&writer); + json_writer_add_array_property (&writer, "envs"); + json_writer_add_array_item (&writer, "PATH=/app/bin:/usr/bin"); + json_writer_add_array_item (&writer, "LD_LIBRARY_PATH=/app/lib"); + json_writer_add_array_item (&writer, "XDG_CONFIG_DIRS=/app/etc/xdg:/etc/xdg"); + json_writer_add_array_item (&writer, "XDG_DATA_DIRS=/app/share:/usr/share"); + json_writer_add_array_item (&writer, "SHELL=/bin/sh"); + json_writer_close_array (&writer); + json_writer_add_string_property (&writer, "cwd", "/"); + json_writer_add_bool_property (&writer, "noNewPrivileges", TRUE); + json_writer_close_struct (&writer); + } + json_writer_add_struct_property (&writer, "root"); + { + json_writer_add_string_property (&writer, "path", "rootfs"); + json_writer_add_bool_property (&writer, "readonly", TRUE); + json_writer_close_struct (&writer); + } + json_writer_add_array_property (&writer, "mounts"); + { + json_writer_add_array_struct (&writer); + { + json_writer_add_string_property (&writer, "destination", "/proc"); + json_writer_add_string_property (&writer, "type", "proc"); + json_writer_add_string_property (&writer, "source", "proc"); + json_writer_close_struct (&writer); + } + json_writer_add_array_struct (&writer); + { + json_writer_add_string_property (&writer, "destination", "/sys"); + json_writer_add_string_property (&writer, "type", "sysfs"); + json_writer_add_string_property (&writer, "source", "sysfs"); + json_writer_add_array_property (&writer, "options"); + { + json_writer_add_array_item (&writer, "nosuid"); + json_writer_add_array_item (&writer, "noexec"); + json_writer_add_array_item (&writer, "nodev"); + json_writer_close_array (&writer); + } + json_writer_close_struct (&writer); + } + json_writer_add_array_struct (&writer); + { + json_writer_add_string_property (&writer, "destination", "/dev"); + json_writer_add_string_property (&writer, "type", "tmpfs"); + json_writer_add_string_property (&writer, "source", "tmpfs"); + json_writer_add_array_property (&writer, "options"); + { + json_writer_add_array_item (&writer, "nosuid"); + json_writer_close_array (&writer); + } + json_writer_close_struct (&writer); + } + json_writer_close_array (&writer); + } + + json_writer_add_struct_property (&writer, "linux"); + { + json_writer_add_string_property (&writer, "rootfsPropagation", "slave"); + json_writer_add_struct_property (&writer, "resources"); + { + json_writer_close_struct (&writer); + } + + json_writer_add_array_property (&writer, "namespaces"); + { + json_writer_add_array_struct (&writer); + { + json_writer_add_string_property (&writer, "type", "pid"); + json_writer_close_struct (&writer); + } + json_writer_add_array_struct (&writer); + { + json_writer_add_string_property (&writer, "type", "mount"); + json_writer_close_struct (&writer); + } + json_writer_close_array (&writer); + } + + json_writer_close_struct (&writer); + } + + json_writer_add_struct_property (&writer, "annotations"); + { + json_writer_close_struct (&writer); + } + + json_writer_close_struct (&writer); + + return writer.str; +} + +#endif + +static gboolean +build_oci (OstreeRepo *repo, GFile *file, + const char *name, const char *full_branch, + GCancellable *cancellable, GError **error) +{ +#if !defined(HAVE_OSTREE_EXPORT_PATH_PREFIX) + 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 xdg-app is not compiled with libarchive support"); + return FALSE; +#else + struct free_write_archive archive *a = NULL; + OstreeRepoExportArchiveOptions opts = { 0, }; + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) files = NULL; + g_autoptr(GFile) export = NULL; + g_autoptr(GFile) metadata = NULL; + g_autoptr(GVariant) commit_data = NULL; + g_autoptr(GVariant) commit_metadata = NULL; + g_autofree char *commit_checksum = NULL; + g_autoptr(GString) str = g_string_new (""); + g_auto(GStrv) ref_parts = g_strsplit (full_branch, "/", -1); + + if (!ostree_repo_resolve_rev (repo, full_branch, FALSE, &commit_checksum, error)) + return FALSE; + + if (!ostree_repo_read_commit (repo, commit_checksum, &root, NULL, NULL, error)) + return FALSE; + + if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, commit_checksum, &commit_data, error)) + return FALSE; + + if (!ostree_repo_read_commit_detached_metadata (repo, commit_checksum, &commit_metadata, cancellable, error)) + return FALSE; + + a = archive_write_new (); + + if (archive_write_set_format_gnutar (a) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + if (archive_write_add_filter_none (a) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + if (archive_write_open_filename (a, gs_file_get_path_cached (file)) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + opts.timestamp_secs = ostree_commit_get_timestamp (commit_data); + + files = g_file_get_child (root, "files"); + export = g_file_get_child (root, "export"); + metadata = g_file_get_child (root, "metadata"); + + if (opt_runtime) + opts.path_prefix = "rootfs/usr/"; + else + opts.path_prefix = "rootfs/app/"; + + { + const char *root_dirs[] = { "dev", "home", "proc", "run", "sys", "tmp", "var", "opt", "srv", "media", "mnt" }; + const char *root_symlinks[] = { + "etc", "usr/etc", + "lib", "usr/lib", + "lib64", "usr/lib64", + "lib32", "usr/lib32", + "bin", "usr/bin", + "sbin", "usr/sbin", + "var/tmp", "/tmp", + "var/run", "/run", + }; + int i; + + /* Add the "other" of /app & /usr */ + if (!add_dir (a, opt_runtime ? "app" : "usr", &opts, error)) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (root_dirs); i++) + if (!add_dir (a, root_dirs[i], &opts, error)) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (root_symlinks); i += 2) + if (!add_symlink (a, root_symlinks[i], root_symlinks[i+1], &opts, error)) + return FALSE; + } + + if (!ostree_repo_export_tree_to_archive (repo, &opts, (OstreeRepoFile*)files, a, + cancellable, error)) + return FALSE; + + if (!opt_runtime && g_file_query_exists (export, NULL)) + { + opts.path_prefix = "export/"; + if (!ostree_repo_export_tree_to_archive (repo, &opts, (OstreeRepoFile*)export, a, + cancellable, error)) + return FALSE; + } + + opts.path_prefix = NULL; + if (!add_file (a, "metadata", repo, metadata, &opts, cancellable, error)) + return FALSE; + + if (!add_file_from_data (a, "ref", + repo, + full_branch, + strlen (full_branch), + &opts, cancellable, error)) + return FALSE; + + if (!add_file_from_data (a, "commit", + repo, + g_variant_get_data (commit_data), + g_variant_get_size (commit_data), + &opts, cancellable, error)) + return FALSE; + + if (commit_metadata != NULL) + { + if (!add_file_from_data (a, "commitmeta", + repo, + g_variant_get_data (commit_metadata), + g_variant_get_size (commit_metadata), + &opts, cancellable, error)) + return FALSE; + } + + str = generate_config_json (ref_parts[2]); + if (!add_file_from_data (a, "config.json", + repo, + str->str, + str->len, + &opts, cancellable, error)) + return FALSE; + + if (archive_write_close (a) != ARCHIVE_OK) + return propagate_libarchive_error (error, a); + + g_print ("WARNING: the oci format produced by xdg-app is experimental and unstable.\n" + "Don't use this for anything but experiments for now\n"); + + return TRUE; +#endif +} + gboolean xdg_app_builtin_build_bundle (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -296,8 +920,16 @@ xdg_app_builtin_build_bundle (int argc, char **argv, GCancellable *cancellable, if (!ostree_repo_open (repo, cancellable, error)) return FALSE; - if (!build_bundle (repo, file, name, full_branch, cancellable, error)) - return FALSE; + if (opt_oci) + { + if (!build_oci (repo, file, name, full_branch, cancellable, error)) + return FALSE; + } + else + { + if (!build_bundle (repo, file, name, full_branch, cancellable, error)) + return FALSE; + } return TRUE; } diff --git a/app/xdg-app-builtins-build-import-bundle.c b/app/xdg-app-builtins-build-import-bundle.c index 00fe6458..06ecc892 100644 --- a/app/xdg-app-builtins-build-import-bundle.c +++ b/app/xdg-app-builtins-build-import-bundle.c @@ -32,12 +32,210 @@ #include "xdg-app-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 xdg-app 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 xdg-app 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_get_child (archive_root, "ref"); + metadata = g_file_get_child (archive_root, "metadata"); + commit = g_file_get_child (archive_root, "commit"); + commitmeta = g_file_get_child (archive_root, "commitmeta"); + + if (!g_file_query_exists (ref, NULL)) + return xdg_app_fail (error, "Required file ref not in tarfile"); + if (!g_file_query_exists (metadata, NULL)) + return xdg_app_fail (error, "Required file metadata not in tarfile"); + if (!g_file_query_exists (commit, NULL)) + return xdg_app_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 xdg_app_fail (error, "Required directory %s not in tarfile", files_source); + + export = g_file_get_child (archive_root, "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 (!xdg_app_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 xdg_app_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 xdg_app_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) @@ -104,8 +302,16 @@ xdg_app_builtin_build_import (int argc, char **argv, GCancellable *cancellable, if (!ostree_repo_open (repo, cancellable, error)) return FALSE; - if (!import_bundle (repo, file, 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; } diff --git a/configure.ac b/configure.ac index 1fef6f8e..9c2110f8 100644 --- a/configure.ac +++ b/configure.ac @@ -140,8 +140,18 @@ PKG_CHECK_MODULES(OSTREE, [libgsystem >= 2015.1 ostree-1 >= 2016.5]) AC_SUBST(OSTREE_CFLAGS) AC_SUBST(OSTREE_LIBS) save_LIBS=$LIBS +save_CFLAGS=$CFLAGS LIBS=$OSTREE_LIBS +CFLAGS=$OSTREE_CFLAGS + +AC_CHECK_MEMBER([OstreeRepoExportArchiveOptions.path_prefix], + [AC_DEFINE([HAVE_OSTREE_EXPORT_PATH_PREFIX], 1, + [Have OstreeRepoExportArchiveOptions.path_prefix])], + , [[#include ]]) + LIBS=$save_LIBS +CFLAGS=$save_CFLAGS + PKG_CHECK_MODULES(FUSE, [fuse]) AC_SUBST(FUSE_CFLAGS)