/* * Copyright © 2014 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 #include #include "libglnx/libglnx.h" #include "flatpak-builtins.h" #include "flatpak-utils.h" static char *opt_subject; static char *opt_body; static char *opt_arch; static gboolean opt_runtime; static gboolean opt_update_appstream; static gboolean opt_no_update_summary; static char **opt_gpg_key_ids; static char **opt_exclude; static char **opt_include; static char *opt_gpg_homedir; static char *opt_files; static char *opt_metadata; static char *opt_timestamp = NULL; static GOptionEntry options[] = { { "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, N_("One line subject"), N_("SUBJECT") }, { "body", 'b', 0, G_OPTION_ARG_STRING, &opt_body, N_("Full description"), N_("BODY") }, { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Architecture to export for (must be host compatible)"), N_("ARCH") }, { "runtime", 'r', 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Commit runtime (/usr), not /app"), NULL }, { "update-appstream", 0, 0, G_OPTION_ARG_NONE, &opt_update_appstream, N_("Update the appstream branch"), NULL }, { "no-update-summary", 0, 0, G_OPTION_ARG_NONE, &opt_no_update_summary, N_("Don't update the summary"), NULL }, { "files", 0, 0, G_OPTION_ARG_STRING, &opt_files, N_("Use alternative directory for the files"), N_("SUBDIR") }, { "metadata", 0, 0, G_OPTION_ARG_STRING, &opt_metadata, N_("Use alternative file for the metadata"), N_("FILE") }, { "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_gpg_key_ids, N_("GPG Key ID to sign the commit with"), N_("KEY-ID") }, { "exclude", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_exclude, N_("Files to exclude"), N_("PATTERN") }, { "include", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_include, N_("Excluded files to include"), N_("PATTERN") }, { "gpg-homedir", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_homedir, N_("GPG Homedir to use when looking for keyrings"), N_("HOMEDIR") }, { "timestamp", 0, 0, G_OPTION_ARG_STRING, &opt_timestamp, "Override the timestamp of the commit", "ISO-8601-TIMESTAMP" }, { NULL } }; static gboolean metadata_get_arch (GFile *file, char **out_arch, GError **error) { g_autofree char *path = NULL; g_autoptr(GKeyFile) keyfile = NULL; g_autofree char *runtime = NULL; g_auto(GStrv) parts = NULL; if (opt_arch != NULL) { *out_arch = g_strdup (opt_arch); return TRUE; } keyfile = g_key_file_new (); path = g_file_get_path (file); if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error)) return FALSE; runtime = g_key_file_get_string (keyfile, "Application", "runtime", NULL); if (runtime == NULL) runtime = g_key_file_get_string (keyfile, "Application", "sdk", NULL); if (runtime == NULL) runtime = g_key_file_get_string (keyfile, "Runtime", "runtime", NULL); if (runtime == NULL) runtime = g_key_file_get_string (keyfile, "Runtime", "sdk", NULL); if (runtime == NULL) { *out_arch = g_strdup (flatpak_get_arch ()); return TRUE; } parts = g_strsplit (runtime, "/", 0); if (g_strv_length (parts) != 3) return flatpak_fail (error, "Failed to determine arch from metadata runtime key: %s", runtime); *out_arch = g_strdup (parts[1]); return TRUE; } static gboolean is_empty_directory (GFile *file, GCancellable *cancellable) { g_autoptr(GFileEnumerator) file_enum = NULL; g_autoptr(GFileInfo) child_info = NULL; file_enum = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, cancellable, NULL); if (!file_enum) return FALSE; child_info = g_file_enumerator_next_file (file_enum, cancellable, NULL); if (child_info) return FALSE; return TRUE; } typedef struct { const char **exclude; const char **include; } CommitData; static gboolean matches_patterns (const char **patterns, const char *path) { int i; if (patterns == NULL) return FALSE; for (i = 0; patterns[i] != NULL; i++) { if (flatpak_path_match_prefix (patterns[i], path) != NULL) return TRUE; } return FALSE; } static OstreeRepoCommitFilterResult commit_filter (OstreeRepo *repo, const char *path, GFileInfo *file_info, CommitData *commit_data) { guint mode; /* No user info */ g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); /* In flatpak, there is no real reason for files to have different * permissions based on the group or user really, everything is * always used readonly for everyone. Having things be writeable * for anyone but the user just causes risks for the system-installed * case. So, we canonicalize the mode to writable only by the user, * readable to all, and executable for all for directories and * files that the user can execute. */ mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) mode = 0755 | S_IFDIR; else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { /* If use can execute, make executable by all */ if (mode & S_IXUSR) mode = 0755 | S_IFREG; else /* otherwise executable by none */ mode = 0644 | S_IFREG; } g_file_info_set_attribute_uint32 (file_info, "unix::mode", mode); if (matches_patterns (commit_data->exclude, path) && !matches_patterns (commit_data->include, path)) { g_debug ("Excluding %s", path); return OSTREE_REPO_COMMIT_FILTER_SKIP; } return OSTREE_REPO_COMMIT_FILTER_ALLOW; } gboolean add_file_to_mtree (GFile *file, const char *name, OstreeRepo *repo, OstreeMutableTree *mtree, GCancellable *cancellable, GError **error) { g_autoptr(GFileInfo) file_info = NULL; g_autoptr(GInputStream) raw_input = NULL; g_autoptr(GInputStream) input = NULL; guint64 length; g_autofree guchar *child_file_csum = NULL; g_autofree char *tmp_checksum = NULL; file_info = g_file_query_info (file, "standard::size", 0, cancellable, error); if (file_info == NULL) return FALSE; g_file_info_set_name (file_info, name); g_file_info_set_file_type (file_info, G_FILE_TYPE_REGULAR); g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); g_file_info_set_attribute_uint32 (file_info, "unix::mode", 0100744); raw_input = (GInputStream *) g_file_read (file, cancellable, error); if (raw_input == NULL) return FALSE; if (!ostree_raw_file_to_content_stream (raw_input, file_info, NULL, &input, &length, cancellable, error)) return FALSE; if (!ostree_repo_write_content (repo, NULL, input, length, &child_file_csum, cancellable, error)) return FALSE; tmp_checksum = ostree_checksum_from_bytes (child_file_csum); if (!ostree_mutable_tree_replace_file (mtree, name, tmp_checksum, error)) return FALSE; return TRUE; } static gboolean find_file_in_tree (GFile *base, const char *filename) { g_autoptr(GFileEnumerator) enumerator = NULL; enumerator = g_file_enumerate_children (base, G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (!enumerator) return FALSE; do { g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, NULL, NULL); GFileType type; const char *name; if (!info) return FALSE; type = g_file_info_get_file_type (info); name = g_file_info_get_name (info); if (type == G_FILE_TYPE_REGULAR && strcmp (name, filename) == 0) return TRUE; else if (type == G_FILE_TYPE_DIRECTORY) { g_autoptr(GFile) dir = g_file_get_child (base, name); if (find_file_in_tree (dir, filename)) return TRUE; } } while (1); return FALSE; } static GFile * convert_app_absolute_path (const char *path, GFile *files) { g_autofree char *exec_path = NULL; if (g_path_is_absolute (path)) { if (g_str_has_prefix (path, "/app/")) exec_path = g_strdup (path + 5); else exec_path = g_strdup (path); } else exec_path = g_strconcat ("bin/", path, NULL); return g_file_resolve_relative_path (files, exec_path); } static gboolean validate_desktop_file (GFile *desktop_file, GFile *export, GFile *files, const char *app_id, char **icon, gboolean *activatable, GError **error) { g_autofree char *path = g_file_get_path (desktop_file); g_autoptr(GSubprocess) subprocess = NULL; g_autofree char *stdout_buf = NULL; g_autofree char *stderr_buf = NULL; g_autoptr(GError) local_error = NULL; g_autoptr(GKeyFile) key_file = NULL; g_autofree char *command = NULL; g_auto(GStrv) argv = NULL; g_autoptr(GFile) bin_file = NULL; if (!g_file_query_exists (desktop_file, NULL)) return TRUE; subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, &local_error, "desktop-file-validate", path, NULL); if (!subprocess) { if (!g_error_matches (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) g_print ("WARNING: Error running desktop-file-validate: %s\n", local_error->message); g_clear_error (&local_error); goto check_refs; } if (!g_subprocess_communicate_utf8 (subprocess, NULL, NULL, &stdout_buf, &stderr_buf, &local_error)) { g_print ("WARNING: Error reading from desktop-file-validate: %s\n", local_error->message); g_clear_error (&local_error); } if (g_subprocess_get_if_exited (subprocess) && g_subprocess_get_exit_status (subprocess) != 0) g_print ("WARNING: Failed to validate desktop file %s: %s\n", path, stdout_buf); check_refs: /* Test that references to other files are valid */ key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, error)) return FALSE; command = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, &local_error); if (!command) { g_print ("WARNING: Can't find Exec key in %s: %s\n", path, local_error->message); g_clear_error (&local_error); } argv = g_strsplit (command, " ", 0); bin_file = convert_app_absolute_path (argv[0], files); if (!g_file_query_exists (bin_file, NULL)) g_print ("WARNING: Binary not found for Exec line in %s: %s\n", path, command); *icon = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL); if (*icon && !g_str_has_prefix (*icon, app_id)) g_print ("WARNING: Icon not matching app id in %s: %s\n", path, *icon); *activatable = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, NULL); return TRUE; } static gboolean validate_icon (const char *icon, GFile *export, const char *app_id, GError **error) { g_autoptr(GFile) icondir = NULL; g_autofree char *png = NULL; g_autofree char *svg = NULL; if (!icon) return TRUE; icondir = g_file_resolve_relative_path (export, "share/icons/hicolor"); png = g_strconcat (icon, ".png", NULL); svg = g_strconcat (icon, ".svg", NULL); if (!find_file_in_tree (icondir, png) && !find_file_in_tree (icondir, svg)) g_print ("WARNING: Icon referenced in desktop file but not exported: %s\n", icon); return TRUE; } static gboolean validate_service_file (GFile *service_file, gboolean activatable, GFile *files, const char *app_id, GError **error) { g_autofree char *path = g_file_get_path (service_file); g_autoptr(GKeyFile) key_file = NULL; g_autofree char *name = NULL; g_autofree char *command = NULL; g_auto(GStrv) argv = NULL; g_autoptr(GFile) bin_file = NULL; if (!g_file_query_exists (service_file, NULL)) { if (activatable) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Desktop file D-Bus activatable, but service file not exported: %s", path); return FALSE; } return TRUE; } key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, error)) return FALSE; name = g_key_file_get_string (key_file, "D-BUS Service", "Name", error); if (!name) { g_prefix_error (error, "Invalid service file %s: ", path); return FALSE; } if (strcmp (name, app_id) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Name in service file %s does not match app id: %s", path, name); return FALSE; } command = g_key_file_get_string (key_file, "D-BUS Service", "Exec", error); if (!command) { g_prefix_error (error, "Invalid service file %s: ", path); return FALSE; } argv = g_strsplit (command, " ", 0); bin_file = convert_app_absolute_path (argv[0], files); if (!g_file_query_exists (bin_file, NULL)) g_print ("WARNING: Binary not found for Exec line in %s: %s\n", path, command); return TRUE; } static gboolean validate_exports (GFile *export, GFile *files, const char *app_id, GError **error) { g_autofree char *desktop_path = NULL; g_autoptr(GFile) desktop_file = NULL; g_autofree char *service_path = NULL; g_autoptr(GFile) service_file = NULL; g_autofree char *icon = NULL; gboolean activatable = FALSE; desktop_path = g_strconcat ("share/applications/", app_id, ".desktop", NULL); desktop_file = g_file_resolve_relative_path (export, desktop_path); if (!validate_desktop_file (desktop_file, export, files, app_id, &icon, &activatable, error)) return FALSE; if (!validate_icon (icon, export, app_id, error)) return FALSE; service_path = g_strconcat ("share/dbus-1/services/", app_id, ".service", NULL); service_file = g_file_resolve_relative_path (export, service_path); if (!validate_service_file (service_file, activatable, files, app_id, error)) return FALSE; return TRUE; } static gboolean collect_extra_data (GKeyFile *metakey, GVariantDict *metadata_dict, GError **error) { g_auto(GStrv) keys = NULL; g_autoptr(GVariantBuilder) extra_data_sources_builder = NULL; g_autoptr(GVariant) extra_data_sources = NULL; int i; keys = g_key_file_get_keys (metakey, "Extra Data", NULL, NULL); if (keys == NULL) return TRUE; extra_data_sources_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ayttays)")); for (i = 0; keys[i] != NULL; i++) { const char *key = keys[i]; if (g_str_has_prefix (key, "uri")) { const char *suffix = key + 3; g_autofree char *uri = NULL; g_autofree char *checksum_key = NULL; g_autofree char *size_key = NULL; g_autofree char *installed_size_key = NULL; g_autofree char *name_key = NULL; g_autofree char *checksum = NULL; g_autofree char *name = NULL; guint64 size, installed_size; checksum_key = g_strconcat ("checksum", suffix, NULL); size_key = g_strconcat ("size", suffix, NULL); installed_size_key = g_strconcat ("installed-size", suffix, NULL); name_key = g_strconcat ("name", suffix, NULL); uri = g_key_file_get_string (metakey, "Extra Data", key, error); if (uri == NULL) return FALSE; if (!g_str_has_prefix (uri, "http:") && !g_str_has_prefix (uri, "https:")) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("Invalid uri type %s, only http/https supported"), uri); return FALSE; } if (g_key_file_has_key (metakey, "Extra Data", name_key, NULL)) { name = g_key_file_get_string (metakey, "Extra Data", name_key, error); if (name == NULL) return FALSE; } else { g_autoptr(GFile) file = g_file_new_for_uri (uri); name = g_file_get_basename (file); if (name == NULL || *name == 0) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("Unable to find basename in %s, specify a name explicitly"), uri); return FALSE; } } if (strchr (name, '/') != NULL) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("No slashes allowed in extra data name")); return FALSE; } checksum = g_key_file_get_string (metakey, "Extra Data", checksum_key, error); if (checksum == NULL) return FALSE; if (!ostree_validate_checksum_string (checksum, NULL)) { g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("Invalid format for sha256 checksum: '%s'"), checksum); return FALSE; } size = g_key_file_get_uint64 (metakey, "Extra Data", size_key, error); if (size == 0) { if (error != NULL && *error == NULL) g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("Extra data sizes of zero not supported")); return FALSE; } installed_size = g_key_file_get_uint64 (metakey, "Extra Data", installed_size_key, NULL); g_variant_builder_add (extra_data_sources_builder, "(^aytt@ay&s)", name, GUINT64_TO_BE (size), GUINT64_TO_BE (installed_size), ostree_checksum_to_bytes_v (checksum), uri); } } extra_data_sources = g_variant_ref_sink (g_variant_builder_end (extra_data_sources_builder)); g_variant_dict_insert_value (metadata_dict, "xa.extra-data-sources", extra_data_sources); return TRUE; } gboolean flatpak_builtin_build_export (int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) files = NULL; g_autoptr(GFile) usr = NULL; g_autoptr(GFile) metadata = NULL; g_autoptr(GFile) export = NULL; g_autoptr(GFile) repofile = NULL; g_autoptr(GFile) root = NULL; g_autoptr(OstreeRepo) repo = NULL; const char *location; const char *directory; const char *branch; g_autofree char *arch = NULL; g_autofree char *full_branch = NULL; g_autofree char *id = NULL; g_autofree char *parent = NULL; g_autofree char *commit_checksum = NULL; g_autofree char *metadata_contents = NULL; g_autofree char *format_size = NULL; g_autoptr(OstreeMutableTree) mtree = NULL; g_autoptr(OstreeMutableTree) files_mtree = NULL; g_autoptr(OstreeMutableTree) export_mtree = NULL; g_autoptr(GKeyFile) metakey = NULL; g_autoptr(GError) my_error = NULL; gsize metadata_size; g_autofree char *subject = NULL; g_autofree char *body = NULL; OstreeRepoTransactionStats stats; g_autoptr(OstreeRepoCommitModifier) modifier = NULL; CommitData commit_data = {0}; g_auto(GVariantDict) metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER; g_autoptr(GVariant) metadata_dict_v = NULL; gboolean is_runtime = FALSE; gboolean is_extension = FALSE; guint64 installed_size = 0,download_size = 0; GTimeVal ts; context = g_option_context_new (_("LOCATION DIRECTORY [BRANCH] - Create a repository from a build directory")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) goto out; if (argc < 3) { usage_error (context, _("LOCATION and DIRECTORY must be specified"), error); goto out; } if (argc > 4) { usage_error (context, _("Too many arguments"), error); goto out; } location = argv[1]; directory = argv[2]; if (argc >= 4) branch = argv[3]; else branch = "master"; if (!flatpak_is_valid_branch (branch, &my_error)) { flatpak_fail (error, _("'%s' is not a valid branch name: %s"), branch, my_error->message); goto out; } base = g_file_new_for_commandline_arg (directory); if (opt_files) files = g_file_resolve_relative_path (base, opt_files); else files = g_file_get_child (base, "files"); if (opt_files) usr = g_file_resolve_relative_path (base, opt_files); else usr = g_file_get_child (base, "usr"); if (opt_metadata) metadata = g_file_resolve_relative_path (base, opt_metadata); else metadata = g_file_get_child (base, "metadata"); export = g_file_get_child (base, "export"); if (!g_file_query_exists (files, cancellable) || !g_file_query_exists (metadata, cancellable)) { flatpak_fail (error, _("Build directory %s not initialized, use flatpak build-init"), directory); goto out; } if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error)) goto out; metakey = g_key_file_new (); if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error)) goto out; id = g_key_file_get_string (metakey, "Application", "name", NULL); if (id == NULL) { id = g_key_file_get_string (metakey, "Runtime", "name", NULL); if (id == NULL) { flatpak_fail (error, _("No name specified in the metadata")); goto out; } is_runtime = TRUE; if (g_key_file_has_group (metakey, "ExtensionOf")) is_extension = TRUE; } if (!(opt_runtime || is_runtime) && !g_file_query_exists (export, cancellable)) { flatpak_fail (error, "Build directory %s not finalized, use flatpak build-finish", directory); goto out; } g_variant_dict_init (&metadata_dict, NULL); if (!collect_extra_data (metakey, &metadata_dict, error)) goto out; if (!(opt_runtime || is_runtime) && !validate_exports (export, files, id, error)) goto out; if (!metadata_get_arch (metadata, &arch, error)) goto out; if (opt_subject) subject = g_strdup (opt_subject); else subject = g_strconcat ("Export ", id, NULL); if (opt_body) body = g_strdup (opt_body); else body = g_strdup_printf ("Name: %s\n" "Arch: %s\n" "Branch: %s\n" "Built with: "PACKAGE_STRING"\n", id, arch, branch, NULL); full_branch = g_strconcat ((opt_runtime || is_runtime) ? "runtime/" : "app/", id, "/", arch, "/", branch, NULL); repofile = g_file_new_for_commandline_arg (location); repo = ostree_repo_new (repofile); if (g_file_query_exists (repofile, cancellable) && !is_empty_directory (repofile, cancellable)) { if (!ostree_repo_open (repo, cancellable, error)) goto out; if (!ostree_repo_resolve_rev (repo, full_branch, TRUE, &parent, error)) goto out; } else { if (!ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, cancellable, error)) goto out; } if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; mtree = ostree_mutable_tree_new (); if (!flatpak_mtree_create_root (repo, mtree, cancellable, error)) goto out; if (!ostree_mutable_tree_ensure_dir (mtree, "files", &files_mtree, error)) goto out; modifier = ostree_repo_commit_modifier_new (OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS, (OstreeRepoCommitFilter) commit_filter, &commit_data, NULL); if (is_extension) { commit_data.exclude = (const char **) opt_exclude; commit_data.include = (const char **) opt_include; if (!ostree_repo_write_directory_to_mtree (repo, files, files_mtree, modifier, cancellable, error)) goto out; commit_data.exclude = NULL; commit_data.include = NULL; } else if (opt_runtime || is_runtime) { commit_data.exclude = (const char **) opt_exclude; commit_data.include = (const char **) opt_include; if (!ostree_repo_write_directory_to_mtree (repo, usr, files_mtree, modifier, cancellable, error)) goto out; commit_data.exclude = NULL; commit_data.include = NULL; } else { commit_data.exclude = (const char **) opt_exclude; commit_data.include = (const char **) opt_include; if (!ostree_repo_write_directory_to_mtree (repo, files, files_mtree, modifier, cancellable, error)) goto out; commit_data.exclude = NULL; commit_data.include = NULL; if (!ostree_mutable_tree_ensure_dir (mtree, "export", &export_mtree, error)) goto out; if (!ostree_repo_write_directory_to_mtree (repo, export, export_mtree, modifier, cancellable, error)) goto out; } if (!add_file_to_mtree (metadata, "metadata", repo, mtree, cancellable, error)) goto out; if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) goto out; if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error)) goto out; g_variant_dict_insert_value (&metadata_dict, "xa.ref", g_variant_new_string (full_branch)); g_variant_dict_insert_value (&metadata_dict, "xa.metadata", g_variant_new_string (metadata_contents)); g_variant_dict_insert_value (&metadata_dict, "xa.installed-size", g_variant_new_uint64 (GUINT64_TO_BE (installed_size))); g_variant_dict_insert_value (&metadata_dict, "xa.download-size", g_variant_new_uint64 (GUINT64_TO_BE (download_size))); metadata_dict_v = g_variant_ref_sink (g_variant_dict_end (&metadata_dict)); /* required for the metadata and the AppStream commits */ if (opt_timestamp != NULL) { if (!g_time_val_from_iso8601 (opt_timestamp, &ts)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not parse '%s'", opt_timestamp); goto out; } } if (opt_timestamp == NULL) { if (!ostree_repo_write_commit (repo, parent, subject, body, metadata_dict_v, OSTREE_REPO_FILE (root), &commit_checksum, cancellable, error)) goto out; } else { if (!ostree_repo_write_commit_with_time (repo, parent, subject, body, metadata_dict_v, OSTREE_REPO_FILE (root), ts.tv_sec, &commit_checksum, cancellable, error)) goto out; } if (opt_gpg_key_ids) { char **iter; for (iter = opt_gpg_key_ids; iter && *iter; iter++) { const char *keyid = *iter; if (!ostree_repo_sign_commit (repo, commit_checksum, keyid, opt_gpg_homedir, cancellable, error)) goto out; } } ostree_repo_transaction_set_ref (repo, NULL, full_branch, commit_checksum); if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error)) goto out; if (opt_update_appstream && !flatpak_repo_generate_appstream (repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, ts.tv_sec, cancellable, error)) return FALSE; if (!opt_no_update_summary && !flatpak_repo_update (repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, cancellable, error)) goto out; format_size = g_format_size (stats.content_bytes_written); g_print ("Commit: %s\n", commit_checksum); g_print ("Metadata Total: %u\n", stats.metadata_objects_total); g_print ("Metadata Written: %u\n", stats.metadata_objects_written); g_print ("Content Total: %u\n", stats.content_objects_total); g_print ("Content Written: %u\n", stats.content_objects_written); g_print ("Content Bytes Written: %" G_GUINT64_FORMAT " (%s)\n", stats.content_bytes_written, format_size); ret = TRUE; out: if (repo) ostree_repo_abort_transaction (repo, cancellable, NULL); return ret; } gboolean flatpak_complete_build_export (FlatpakCompletion *completion) { g_autoptr(GOptionContext) context = NULL; context = g_option_context_new (""); if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, NULL, NULL)) return FALSE; switch (completion->argc) { case 0: case 1: /* LOCATION */ flatpak_complete_options (completion, global_entries); flatpak_complete_options (completion, options); flatpak_complete_dir (completion); break; case 2: /* DIR */ flatpak_complete_dir (completion); break; } return TRUE; }