summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2016-04-20 12:05:32 +0200
committerAlexander Larsson <alexl@redhat.com>2016-04-28 20:29:49 +0200
commitc0f2304f8163d19548fd9ca11737d90cb912ad85 (patch)
treec13896582107d84b1778897edb121fb9b61e3099 /app
parentacd84a454aa6db429e0f0f87a2a958c8a7fcd1c7 (diff)
downloadxdg-app-c0f2304f8163d19548fd9ca11737d90cb912ad85.tar.gz
Experimental version of OCI support
This lets you export and import a runtime or an application into a tarball that explodes to match the oci runtime spec. This goal of this is to interchange xdg-app apps with other systems that support OCI. Note that this is highly experimental, because the oci specs are in flux, and in fact we should probably use the OCI image spec instead of the runtime spec, but its not yet finished enough for us to use it. So, don't rely on this for now other than to experiment with it.
Diffstat (limited to 'app')
-rw-r--r--app/xdg-app-builtins-build-bundle.c636
-rw-r--r--app/xdg-app-builtins-build-import-bundle.c210
2 files changed, 842 insertions, 4 deletions
diff --git a/app/xdg-app-builtins-build-bundle.c b/app/xdg-app-builtins-build-bundle.c
index b56a323..57d3039 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 <archive.h>
+#include <archive_entry.h>
+#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 00fe645..06ecc89 100644
--- a/app/xdg-app-builtins-build-import-bundle.c
+++ b/app/xdg-app-builtins-build-import-bundle.c
@@ -32,13 +32,211 @@
#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;
}