/* * 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 #include "libgsystem.h" #include "libglnx/libglnx.h" #include "xdg-app-builtins.h" #include "xdg-app-utils.h" #include "xdg-app-chain-input-stream.h" static char *opt_arch; static char *opt_repo_url; static gboolean opt_runtime = FALSE; static char **opt_gpg_file; 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" }, { NULL } }; static GBytes * read_gpg_data (GCancellable *cancellable, GError **error) { g_autoptr(GInputStream) source_stream = NULL; g_autoptr(GOutputStream) mem_stream = NULL; guint n_keyrings = 0; g_autoptr(GPtrArray) streams = NULL; if (opt_gpg_file != NULL) n_keyrings = g_strv_length (opt_gpg_file); guint ii; streams = g_ptr_array_new_with_free_func (g_object_unref); for (ii = 0; ii < n_keyrings; ii++) { GInputStream *input_stream = NULL; if (strcmp (opt_gpg_file[ii], "-") == 0) { input_stream = g_unix_input_stream_new (STDIN_FILENO, FALSE); } else { g_autoptr(GFile) file = g_file_new_for_path (opt_gpg_file[ii]); input_stream = G_INPUT_STREAM(g_file_read (file, cancellable, error)); if (input_stream == NULL) return NULL; } /* Takes ownership. */ g_ptr_array_add (streams, input_stream); } /* Chain together all the --keyring options as one long stream. */ source_stream = (GInputStream *) xdg_app_chain_input_stream_new (streams); mem_stream = g_memory_output_stream_new_resizable (); if (g_output_stream_splice (mem_stream, source_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, cancellable, error) < 0) return NULL; return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_stream)); } gboolean xdg_app_builtin_build_bundle (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; g_autoptr(GBytes) gpg_data = NULL; const char *location; const char *filename; const char *name; const char *branch; g_autofree char *full_branch = NULL; g_autofree char *commit_checksum = NULL; GVariantBuilder metadata_builder; GVariantBuilder param_builder; context = g_option_context_new ("LOCATION FILENAME NAME [BRANCH] - Create a single file bundle from a local repository"); if (!xdg_app_option_context_parse (context, options, &argc, &argv, XDG_APP_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) return FALSE; if (argc < 4) return usage_error (context, "LOCATION, FILENAME and NAME must be specified", error); location = argv[1]; filename = argv[2]; name = argv[3]; if (argc >= 5) branch = argv[4]; else branch = "master"; repofile = g_file_new_for_commandline_arg (location); repo = ostree_repo_new (repofile); if (!xdg_app_supports_bundles (repo)) return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles"); if (!g_file_query_exists (repofile, cancellable)) return xdg_app_fail (error, "'%s' is not a valid repository", location); file = g_file_new_for_commandline_arg (filename); if (!xdg_app_is_valid_branch (name)) return xdg_app_fail (error, "'%s' is not a valid name", name); if (!xdg_app_is_valid_branch (branch)) return xdg_app_fail (error, "'%s' is not a valid branch name", branch); if (opt_runtime) full_branch = xdg_app_build_runtime_ref (name, branch, opt_arch); else full_branch = xdg_app_build_app_ref (name, branch, opt_arch); if (!ostree_repo_open (repo, cancellable, error)) return FALSE; if (!ostree_repo_resolve_rev (repo, full_branch, FALSE, &commit_checksum, error)) return FALSE; g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&metadata_builder, "{sv}", "ref", g_variant_new_string (full_branch)); if (opt_repo_url) g_variant_builder_add (&metadata_builder, "{sv}", "origin", g_variant_new_string (opt_repo_url)); if (opt_gpg_file != NULL) { gpg_data = read_gpg_data (cancellable, error); if (gpg_data == NULL) return FALSE; } if (gpg_data) g_variant_builder_add (&metadata_builder, "{sv}", "gpg-keys", g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, g_bytes_get_data (gpg_data, NULL), g_bytes_get_size (gpg_data), 1)); g_variant_builder_init (¶m_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (¶m_builder, "{sv}", "min-fallback-size", g_variant_new_uint32 (0)); g_variant_builder_add (¶m_builder, "{sv}", "compression", g_variant_new_byte ('x')); g_variant_builder_add (¶m_builder, "{sv}", "bsdiff-enabled", g_variant_new_boolean (FALSE)); g_variant_builder_add (¶m_builder, "{sv}", "inline-parts", g_variant_new_boolean (TRUE)); g_variant_builder_add (¶m_builder, "{sv}", "include-detached", g_variant_new_boolean (TRUE)); g_variant_builder_add (¶m_builder, "{sv}", "filename", g_variant_new_bytestring (gs_file_get_path_cached (file))); if (!ostree_repo_static_delta_generate (repo, OSTREE_STATIC_DELTA_GENERATE_OPT_LOWLATENCY, /* from */ NULL, commit_checksum, g_variant_builder_end (&metadata_builder), g_variant_builder_end (¶m_builder), cancellable, error)) return FALSE; return TRUE; }