gboolean ot_admin_builtin_switch (int argc, char **argv, OstreeSysroot *sysroot, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GOptionContext *context; const char *new_provided_refspec = NULL; gs_unref_object OstreeRepo *repo = NULL; gs_free char *origin_refspec = NULL; gs_free char *origin_remote = NULL; gs_free char *origin_ref = NULL; gs_free char *new_remote = NULL; gs_free char *new_ref = NULL; gs_free char *new_refspec = NULL; gs_free char *new_revision = NULL; gs_unref_object GFile *deployment_path = NULL; gs_unref_object GFile *deployment_origin_path = NULL; gs_unref_object OstreeDeployment *merge_deployment = NULL; gs_unref_object OstreeDeployment *new_deployment = NULL; gs_unref_object OstreeSysrootUpgrader *upgrader = NULL; gs_unref_object OstreeAsyncProgress *progress = NULL; gboolean changed; GSConsole *console = NULL; gboolean in_status_line = FALSE; GKeyFile *old_origin; GKeyFile *new_origin = NULL; context = g_option_context_new ("REF - Construct new tree from current origin and deploy it, if it changed"); g_option_context_add_main_entries (context, options, NULL); if (!g_option_context_parse (context, &argc, &argv, error)) goto out; if (argc < 2) { ot_util_usage_error (context, "REF must be specified", error); goto out; } new_provided_refspec = argv[1]; if (!ostree_sysroot_load (sysroot, cancellable, error)) goto out; upgrader = ostree_sysroot_upgrader_new_for_os (sysroot, opt_osname, cancellable, error); if (!upgrader) goto out; old_origin = ostree_sysroot_upgrader_get_origin (upgrader); origin_refspec = g_key_file_get_string (old_origin, "origin", "refspec", NULL); if (!ostree_parse_refspec (origin_refspec, &origin_remote, &origin_ref, error)) goto out; /* Allow just switching remotes */ if (g_str_has_suffix (new_provided_refspec, ":")) { new_remote = g_strdup (new_provided_refspec); new_remote[strlen(new_remote)-1] = '\0'; new_ref = g_strdup (origin_ref); } else { if (!ostree_parse_refspec (new_provided_refspec, &new_remote, &new_ref, error)) goto out; } if (!new_remote) new_refspec = g_strconcat (origin_remote, ":", new_ref, NULL); else new_refspec = g_strconcat (new_remote, ":", new_ref, NULL); if (strcmp (origin_refspec, new_refspec) == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Old and new refs are equal: %s", new_refspec); goto out; } new_origin = ostree_sysroot_origin_new_from_refspec (sysroot, new_refspec); if (!ostree_sysroot_upgrader_set_origin (upgrader, new_origin, cancellable, error)) goto out; console = gs_console_get (); if (console) { gs_console_begin_status_line (console, "", NULL, NULL); in_status_line = TRUE; progress = ostree_async_progress_new_and_connect (ot_common_pull_progress, console); } /* Always allow older...there's not going to be a chronological * relationship necessarily. */ if (!ostree_sysroot_upgrader_pull (upgrader, 0, OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER, progress, &changed, cancellable, error)) goto out; if (in_status_line) { gs_console_end_status_line (console, NULL, NULL); in_status_line = FALSE; } if (!ostree_sysroot_upgrader_deploy (upgrader, cancellable, error)) goto out; if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) goto out; if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; g_print ("Deleting ref '%s:%s'\n", origin_remote, origin_ref); ostree_repo_transaction_set_ref (repo, origin_remote, origin_ref, NULL); if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; { gs_unref_object GFile *real_sysroot = g_file_new_for_path ("/"); if (opt_reboot && g_file_equal (ostree_sysroot_get_path (sysroot), real_sysroot)) { gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT, cancellable, error, "systemctl", "reboot", NULL); } } ret = TRUE; out: if (in_status_line) gs_console_end_status_line (console, NULL, NULL); if (new_origin) g_key_file_unref (new_origin); if (context) g_option_context_free (context); return ret; }
gboolean ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; glnx_unref_object OstreeRepo *repo = NULL; gboolean ret = FALSE; gboolean skip_commit = FALSE; g_autoptr(GFile) object_to_commit = NULL; g_autofree char *parent = NULL; g_autofree char *commit_checksum = NULL; g_autoptr(GFile) root = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) detached_metadata = NULL; glnx_unref_object OstreeMutableTree *mtree = NULL; g_autofree char *tree_type = NULL; g_autoptr(GHashTable) mode_adds = NULL; g_autoptr(GHashTable) skip_list = NULL; OstreeRepoCommitModifierFlags flags = 0; OstreeRepoCommitModifier *modifier = NULL; OstreeRepoTransactionStats stats; struct CommitFilterData filter_data = { 0, }; g_autofree char *commit_body = NULL; context = g_option_context_new ("[PATH] - Commit a new revision"); if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error)) goto out; if (!ostree_ensure_repo_writable (repo, error)) goto out; if (opt_statoverride_file) { mode_adds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!parse_file_by_line (opt_statoverride_file, handle_statoverride_line, mode_adds, cancellable, error)) goto out; } if (opt_skiplist_file) { skip_list = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!parse_file_by_line (opt_skiplist_file, handle_skiplist_line, skip_list, cancellable, error)) goto out; } if (opt_metadata_strings) { if (!parse_keyvalue_strings (opt_metadata_strings, &metadata, error)) goto out; } if (opt_detached_metadata_strings) { if (!parse_keyvalue_strings (opt_detached_metadata_strings, &detached_metadata, error)) goto out; } if (!(opt_branch || opt_orphan)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "A branch must be specified with --branch, or use --orphan"); goto out; } if (opt_no_xattrs) flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS; if (opt_canonical_permissions) flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_CANONICAL_PERMISSIONS; if (opt_generate_sizes) flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES; if (opt_disable_fsync) ostree_repo_set_disable_fsync (repo, TRUE); if (flags != 0 || opt_owner_uid >= 0 || opt_owner_gid >= 0 || opt_statoverride_file != NULL || opt_skiplist_file != NULL || opt_no_xattrs) { filter_data.mode_adds = mode_adds; filter_data.skip_list = skip_list; modifier = ostree_repo_commit_modifier_new (flags, commit_filter, &filter_data, NULL); } if (opt_parent) { if (g_str_equal (opt_parent, "none")) parent = NULL; else { if (!ostree_validate_checksum_string (opt_parent, error)) goto out; parent = g_strdup (opt_parent); } } else if (!opt_orphan) { if (!ostree_repo_resolve_rev (repo, opt_branch, TRUE, &parent, error)) { if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) { /* A folder exists with the specified ref name, * which is handled by _ostree_repo_write_ref */ g_clear_error (error); } else goto out; } } if (opt_editor) { if (!commit_editor (repo, opt_branch, &opt_subject, &commit_body, cancellable, error)) goto out; } else if (opt_body_file) { commit_body = glnx_file_get_contents_utf8_at (AT_FDCWD, opt_body_file, NULL, cancellable, error); if (!commit_body) goto out; } else if (opt_body) commit_body = g_strdup (opt_body); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; if (opt_link_checkout_speedup && !ostree_repo_scan_hardlinks (repo, cancellable, error)) goto out; mtree = ostree_mutable_tree_new (); if (argc <= 1 && (opt_trees == NULL || opt_trees[0] == NULL)) { if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, modifier, cancellable, error)) goto out; } else if (opt_trees != NULL) { const char *const*tree_iter; const char *tree; const char *eq; for (tree_iter = (const char *const*)opt_trees; *tree_iter; tree_iter++) { tree = *tree_iter; eq = strchr (tree, '='); if (!eq) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing type in tree specification '%s'", tree); goto out; } g_free (tree_type); tree_type = g_strndup (tree, eq - tree); tree = eq + 1; g_clear_object (&object_to_commit); if (strcmp (tree_type, "dir") == 0) { if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, tree, mtree, modifier, cancellable, error)) goto out; } else if (strcmp (tree_type, "tar") == 0) { object_to_commit = g_file_new_for_path (tree); if (!ostree_repo_write_archive_to_mtree (repo, object_to_commit, mtree, modifier, opt_tar_autocreate_parents, cancellable, error)) goto out; } else if (strcmp (tree_type, "ref") == 0) { if (!ostree_repo_read_commit (repo, tree, &object_to_commit, NULL, cancellable, error)) goto out; if (!ostree_repo_write_directory_to_mtree (repo, object_to_commit, mtree, modifier, cancellable, error)) goto out; } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid tree type specification '%s'", tree_type); goto out; } } } else { g_assert (argc > 1); if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, argv[1], mtree, modifier, cancellable, error)) goto out; } if (mode_adds && g_hash_table_size (mode_adds) > 0) { GHashTableIter hash_iter; gpointer key, value; g_hash_table_iter_init (&hash_iter, mode_adds); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { g_printerr ("Unmatched statoverride path: %s\n", (char*)key); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unmatched statoverride paths"); goto out; } if (skip_list && g_hash_table_size (skip_list) > 0) { GHashTableIter hash_iter; gpointer key; g_hash_table_iter_init (&hash_iter, skip_list); while (g_hash_table_iter_next (&hash_iter, &key, NULL)) { g_printerr ("Unmatched skip-list path: %s\n", (char*)key); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unmatched skip-list paths"); goto out; } if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) goto out; if (opt_skip_if_unchanged && parent) { g_autoptr(GFile) parent_root; if (!ostree_repo_read_commit (repo, parent, &parent_root, NULL, cancellable, error)) goto out; if (g_file_equal (root, parent_root)) skip_commit = TRUE; } if (!skip_commit) { gboolean update_summary; guint64 timestamp; if (!opt_timestamp) { GDateTime *now = g_date_time_new_now_utc (); timestamp = g_date_time_to_unix (now); g_date_time_unref (now); if (!ostree_repo_write_commit (repo, parent, opt_subject, commit_body, metadata, OSTREE_REPO_FILE (root), &commit_checksum, cancellable, error)) goto out; } else { struct timespec ts; if (!parse_datetime (&ts, opt_timestamp, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Could not parse '%s'", opt_timestamp); goto out; } timestamp = ts.tv_sec; if (!ostree_repo_write_commit_with_time (repo, parent, opt_subject, commit_body, metadata, OSTREE_REPO_FILE (root), timestamp, &commit_checksum, cancellable, error)) goto out; } if (detached_metadata) { if (!ostree_repo_write_commit_detached_metadata (repo, commit_checksum, detached_metadata, cancellable, error)) goto out; } if (opt_key_ids) { char **iter; for (iter = opt_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; } } if (opt_branch) ostree_repo_transaction_set_ref (repo, NULL, opt_branch, commit_checksum); else g_assert (opt_orphan); if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error)) goto out; /* The default for this option is FALSE, even for archive-z2 repos, * because ostree supports multiple processes committing to the same * repo (but different refs) concurrently, and in fact gnome-continuous * actually does this. In that context it's best to update the summary * explicitly instead of automatically here. */ if (!ot_keyfile_get_boolean_with_default (ostree_repo_get_config (repo), "core", "commit-update-summary", FALSE, &update_summary, error)) goto out; if (update_summary && !ostree_repo_regenerate_summary (repo, NULL, cancellable, error)) goto out; } else { commit_checksum = g_strdup (parent); } if (opt_table_output) { 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 "\n", stats.content_bytes_written); } else { g_print ("%s\n", commit_checksum); } ret = TRUE; out: if (repo) ostree_repo_abort_transaction (repo, cancellable, NULL); if (modifier) ostree_repo_commit_modifier_unref (modifier); return ret; }
gboolean install_bundle (XdgAppDir *dir, GOptionContext *context, int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autoptr(GFile) deploy_base = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) gpg_tmp_file = NULL; const char *filename; g_autofree char *ref = NULL; g_autofree char *origin = NULL; gboolean created_deploy_base = FALSE; gboolean added_remote = FALSE; g_autofree char *to_checksum = NULL; g_auto(GStrv) parts = NULL; g_autoptr(GBytes) gpg_data = NULL; g_autofree char *remote = NULL; OstreeRepo *repo; g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; g_autoptr(GError) my_error = NULL; g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; if (argc < 2) return usage_error (context, "bundle filename must be specified", error); filename = argv[1]; repo = xdg_app_dir_get_repo (dir); if (!xdg_app_supports_bundles (repo)) return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles"); if (!xdg_app_dir_lock (dir, &lock, cancellable, error)) goto out; file = g_file_new_for_commandline_arg (filename); { g_autoptr(GVariant) delta = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GVariant) to_csum_v = NULL; g_autoptr(GVariant) gpg_value = NULL; GMappedFile *mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error); if (mfile == NULL) return FALSE; bytes = g_mapped_file_get_bytes (mfile); g_mapped_file_unref (mfile); delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE); g_variant_ref_sink (delta); to_csum_v = g_variant_get_child_value (delta, 3); if (!ostree_validate_structureof_csum_v (to_csum_v, error)) return FALSE; to_checksum = ostree_checksum_from_bytes_v (to_csum_v); metadata = g_variant_get_child_value (delta, 0); if (!g_variant_lookup (metadata, "ref", "s", &ref)) return xdg_app_fail (error, "Invalid bundle, no ref in metadata"); if (!g_variant_lookup (metadata, "origin", "s", &origin)) origin = NULL; gpg_value = g_variant_lookup_value (metadata, "gpg-keys", G_VARIANT_TYPE("ay")); if (gpg_value) { gsize n_elements; const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); gpg_data = g_bytes_new (data, n_elements); } } parts = xdg_app_decompose_ref (ref, error); if (parts == NULL) return FALSE; deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (g_file_query_exists (deploy_base, cancellable)) return xdg_app_fail (error, "%s branch %s already installed", parts[1], parts[3]); if (opt_gpg_file != NULL) { /* Override gpg_data from file */ gpg_data = read_gpg_data (cancellable, error); if (gpg_data == NULL) return FALSE; } /* Add a remote for later updates */ if (origin != NULL) { g_auto(GStrv) remotes = ostree_repo_remote_list (repo, NULL); int version = 0; do { g_autofree char *name = NULL; if (version == 0) name = g_strdup_printf ("%s-origin", parts[1]); else name = g_strdup_printf ("%s-%d-origin", parts[1], version); version++; if (remotes == NULL || !g_strv_contains ((const char * const *) remotes, name)) remote = g_steal_pointer (&name); } while (remote == NULL); } if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum); if (!ostree_repo_static_delta_execute_offline (repo, file, FALSE, cancellable, error)) return FALSE; if (gpg_data) { g_autoptr(GFileIOStream) stream; GOutputStream *o; gpg_tmp_file = g_file_new_tmp (".xdg-app-XXXXXX", &stream, error); if (gpg_tmp_file == NULL) return FALSE; o = g_io_stream_get_output_stream (G_IO_STREAM (stream)); if (!g_output_stream_write_all (o, g_bytes_get_data (gpg_data, NULL), g_bytes_get_size (gpg_data), NULL, cancellable, error)) return FALSE; } gpg_result = ostree_repo_verify_commit_ext (repo, to_checksum, NULL, gpg_tmp_file, cancellable, &my_error); if (gpg_tmp_file) g_file_delete (gpg_tmp_file, cancellable, NULL); if (gpg_result == NULL) { /* NOT_FOUND means no gpg signature, we ignore this *if* there * is no gpg key specified in the bundle or by the user */ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && gpg_data == NULL) g_clear_error (&my_error); else { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } else { /* If there is no valid gpg signature we fail, unless there is no gpg key specified (on the command line or in the file) because then we trust the source bundle. */ if (ostree_gpg_verify_result_count_valid (gpg_result) == 0 && gpg_data != NULL) return xdg_app_fail (error, "GPG signatures found, but none are in trusted keyring"); } if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) return FALSE; if (!g_file_make_directory_with_parents (deploy_base, cancellable, error)) return FALSE; /* From here we need to goto out on error, to clean up */ created_deploy_base = TRUE; if (remote) { g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_autofree char *basename = g_file_get_basename (file); g_variant_builder_add (optbuilder, "{s@v}", "xa.title", g_variant_new_variant (g_variant_new_string (basename))); g_variant_builder_add (optbuilder, "{s@v}", "xa.noenumerate", g_variant_new_variant (g_variant_new_boolean (TRUE))); g_variant_builder_add (optbuilder, "{s@v}", "xa.prio", g_variant_new_variant (g_variant_new_string ("0"))); if (!ostree_repo_remote_add (repo, remote, origin, g_variant_builder_end (optbuilder), cancellable, error)) goto out; added_remote = TRUE; if (gpg_data) { g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data); if (!ostree_repo_remote_gpg_import (repo, remote, gpg_data_as_stream, NULL, NULL, cancellable, error)) goto out; } if (!xdg_app_dir_set_origin (dir, ref, remote, cancellable, error)) goto out; } if (!xdg_app_dir_deploy (dir, ref, to_checksum, cancellable, error)) goto out; if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error)) goto out; if (strcmp (parts[0], "app") == 0) { if (!xdg_app_dir_update_exports (dir, parts[1], cancellable, error)) goto out; } glnx_release_lock_file (&lock); xdg_app_dir_cleanup_removed (dir, cancellable, NULL); if (!xdg_app_dir_mark_changed (dir, error)) goto out; ret = TRUE; out: if (created_deploy_base && !ret) gs_shutil_rm_rf (deploy_base, cancellable, NULL); if (added_remote && !ret) ostree_repo_remote_delete (repo, remote, NULL, NULL); return ret; }
gboolean rpmostree_commit (GFile *rootfs, OstreeRepo *repo, const char *refname, GVariant *metadata, const char *gpg_keyid, gboolean enable_selinux, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object OstreeMutableTree *mtree = NULL; OstreeRepoCommitModifier *commit_modifier = NULL; gs_free char *parent_revision = NULL; gs_free char *new_revision = NULL; gs_unref_object GFile *root_tree = NULL; gs_unref_object OstreeSePolicy *sepolicy = NULL; gs_fd_close int rootfs_fd = -1; /* hardcode targeted policy for now */ if (enable_selinux) { if (!workaround_selinux_cross_labeling (rootfs, cancellable, error)) goto out; sepolicy = ostree_sepolicy_new (rootfs, cancellable, error); if (!sepolicy) goto out; } g_print ("Committing '%s' ...\n", gs_file_get_path_cached (rootfs)); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; if (!gs_file_open_dir_fd (rootfs, &rootfs_fd, cancellable, error)) goto out; mtree = ostree_mutable_tree_new (); commit_modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL); ostree_repo_commit_modifier_set_xattr_callback (commit_modifier, read_xattrs_cb, NULL, GINT_TO_POINTER (rootfs_fd)); if (sepolicy) { const char *policy_name = ostree_sepolicy_get_name (sepolicy); g_print ("Labeling with SELinux policy '%s'\n", policy_name); ostree_repo_commit_modifier_set_sepolicy (commit_modifier, sepolicy); } if (!ostree_repo_write_directory_to_mtree (repo, rootfs, mtree, commit_modifier, cancellable, error)) goto out; if (!ostree_repo_write_mtree (repo, mtree, &root_tree, cancellable, error)) goto out; if (!ostree_repo_resolve_rev (repo, refname, TRUE, &parent_revision, error)) goto out; if (!ostree_repo_write_commit (repo, parent_revision, "", "", metadata, (OstreeRepoFile*)root_tree, &new_revision, cancellable, error)) goto out; if (gpg_keyid) { g_print ("Signing commit %s with key %s\n", new_revision, gpg_keyid); if (!ostree_repo_sign_commit (repo, new_revision, gpg_keyid, NULL, cancellable, error)) goto out; } ostree_repo_transaction_set_ref (repo, NULL, refname, new_revision); if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; g_print ("%s => %s\n", refname, new_revision); if (!g_getenv ("RPM_OSTREE_PRESERVE_ROOTFS")) (void) gs_shutil_rm_rf (rootfs, NULL, NULL); else g_print ("Preserved %s\n", gs_file_get_path_cached (rootfs)); ret = TRUE; out: return ret; }
gboolean flatpak_builtin_build_commit_from (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) dst_repofile = NULL; g_autoptr(OstreeRepo) dst_repo = NULL; g_autoptr(GFile) src_repofile = NULL; g_autoptr(OstreeRepo) src_repo = NULL; g_autofree char *src_repo_uri = NULL; const char *dst_repo_arg; const char **dst_refs; int n_dst_refs = 0; g_autoptr(FlatpakRepoTransaction) transaction = NULL; g_autoptr(GPtrArray) src_refs = NULL; g_autoptr(GPtrArray) resolved_src_refs = NULL; OstreeRepoCommitState src_commit_state; struct timespec ts; guint64 timestamp; int i; context = g_option_context_new (_("DST-REPO [DST-REF…] - Make a new commit from existing commits")); 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)) return FALSE; if (argc < 2) return usage_error (context, _("DST-REPO must be specified"), error); dst_repo_arg = argv[1]; dst_refs = (const char **) argv + 2; n_dst_refs = argc - 2; if (opt_src_repo == NULL && n_dst_refs != 1) return usage_error (context, _("If --src-repo is not specified, exactly one destination ref must be specified"), error); if (opt_src_ref != NULL && n_dst_refs != 1) return usage_error (context, _("If --src-ref is specified, exactly one destination ref must be specified"), error); if (opt_src_repo == NULL && opt_src_ref == NULL) return flatpak_fail (error, _("Either --src-repo or --src-ref must be specified.")); if (opt_timestamp) { if (!parse_datetime (&ts, opt_timestamp, NULL)) return flatpak_fail (error, _("Could not parse '%s'"), opt_timestamp); } dst_repofile = g_file_new_for_commandline_arg (dst_repo_arg); if (!g_file_query_exists (dst_repofile, cancellable)) return flatpak_fail (error, _("'%s' is not a valid repository"), dst_repo_arg); dst_repo = ostree_repo_new (dst_repofile); if (!ostree_repo_open (dst_repo, cancellable, error)) return FALSE; if (opt_disable_fsync) ostree_repo_set_disable_fsync (dst_repo, TRUE); if (opt_src_repo) { src_repofile = g_file_new_for_commandline_arg (opt_src_repo); if (!g_file_query_exists (src_repofile, cancellable)) return flatpak_fail (error, _("'%s' is not a valid repository"), opt_src_repo); src_repo_uri = g_file_get_uri (src_repofile); src_repo = ostree_repo_new (src_repofile); if (!ostree_repo_open (src_repo, cancellable, error)) return FALSE; } else { src_repo = g_object_ref (dst_repo); } src_refs = g_ptr_array_new_with_free_func (g_free); if (opt_src_ref) { g_assert (n_dst_refs == 1); g_ptr_array_add (src_refs, g_strdup (opt_src_ref)); } else { g_assert (opt_src_repo != NULL); if (n_dst_refs == 0) { g_autofree const char **keys = NULL; g_autoptr(GHashTable) all_src_refs = NULL; if (!ostree_repo_list_refs (src_repo, NULL, &all_src_refs, cancellable, error)) return FALSE; keys = (const char **) g_hash_table_get_keys_as_array (all_src_refs, NULL); for (i = 0; keys[i] != NULL; i++) { if (g_str_has_prefix (keys[i], "runtime/") || g_str_has_prefix (keys[i], "app/")) g_ptr_array_add (src_refs, g_strdup (keys[i])); } n_dst_refs = src_refs->len; dst_refs = (const char **) src_refs->pdata; } else { for (i = 0; i < n_dst_refs; i++) g_ptr_array_add (src_refs, g_strdup (dst_refs[i])); } } resolved_src_refs = g_ptr_array_new_with_free_func (g_free); for (i = 0; i < src_refs->len; i++) { const char *src_ref = g_ptr_array_index (src_refs, i); char *resolved_ref; if (!ostree_repo_resolve_rev (src_repo, src_ref, FALSE, &resolved_ref, error)) return FALSE; g_ptr_array_add (resolved_src_refs, resolved_ref); } if (src_repo_uri != NULL) { OstreeRepoPullFlags pullflags = 0; GVariantBuilder builder; g_autoptr(OstreeAsyncProgress) progress = NULL; g_auto(GLnxConsoleRef) console = { 0, }; g_autoptr(GVariant) options = NULL; gboolean res; if (opt_untrusted) pullflags |= OSTREE_REPO_PULL_FLAGS_UNTRUSTED; glnx_console_lock (&console); if (console.is_tty) progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{s@v}", "flags", g_variant_new_variant (g_variant_new_int32 (pullflags))); g_variant_builder_add (&builder, "{s@v}", "refs", g_variant_new_variant (g_variant_new_strv ((const char * const *) resolved_src_refs->pdata, resolved_src_refs->len))); g_variant_builder_add (&builder, "{s@v}", "depth", g_variant_new_variant (g_variant_new_int32 (0))); options = g_variant_ref_sink (g_variant_builder_end (&builder)); res = ostree_repo_pull_with_options (dst_repo, src_repo_uri, options, progress, cancellable, error); if (progress) ostree_async_progress_finish (progress); if (!res) return FALSE; } /* By now we have the commit with commit_id==resolved_ref and dependencies in dst_repo. We now create a new * commit based on the toplevel tree ref from that commit. * This is equivalent to: * ostree commit --skip-if-unchanged --repo=${destrepo} --tree=ref=${resolved_ref} */ transaction = flatpak_repo_transaction_start (dst_repo, cancellable, error); if (transaction == NULL) return FALSE; for (i = 0; i < resolved_src_refs->len; i++) { const char *dst_ref = dst_refs[i]; const char *resolved_ref = g_ptr_array_index (resolved_src_refs, i); g_autofree char *dst_parent = NULL; g_autoptr(GFile) dst_parent_root = NULL; g_autoptr(GFile) src_ref_root = NULL; g_autoptr(GVariant) src_commitv = NULL; g_autoptr(GVariant) dst_commitv = NULL; g_autoptr(OstreeMutableTree) mtree = NULL; g_autoptr(GFile) dst_root = NULL; g_autoptr(GVariant) commitv_metadata = NULL; g_autoptr(GVariant) metadata = NULL; const char *subject; const char *body; g_autofree char *commit_checksum = NULL; GVariantBuilder metadata_builder; gint j; const char *dst_collection_id = NULL; const char *main_collection_id = NULL; g_autoptr(GPtrArray) collection_ids = NULL; if (!ostree_repo_resolve_rev (dst_repo, dst_ref, TRUE, &dst_parent, error)) return FALSE; if (dst_parent != NULL && !ostree_repo_read_commit (dst_repo, dst_parent, &dst_parent_root, NULL, cancellable, error)) return FALSE; if (!ostree_repo_read_commit (dst_repo, resolved_ref, &src_ref_root, NULL, cancellable, error)) return FALSE; if (!ostree_repo_load_commit (dst_repo, resolved_ref, &src_commitv, &src_commit_state, error)) return FALSE; if (src_commit_state & OSTREE_REPO_COMMIT_STATE_PARTIAL) return flatpak_fail (error, _("Can't commit from partial source commit.")); /* Don't create a new commit if this is the same tree */ if (!opt_force && dst_parent_root != NULL && g_file_equal (dst_parent_root, src_ref_root)) { g_print (_("%s: no change\n"), dst_ref); continue; } mtree = ostree_mutable_tree_new (); if (!ostree_repo_write_directory_to_mtree (dst_repo, src_ref_root, mtree, NULL, cancellable, error)) return FALSE; if (!ostree_repo_write_mtree (dst_repo, mtree, &dst_root, cancellable, error)) return FALSE; commitv_metadata = g_variant_get_child_value (src_commitv, 0); g_variant_get_child (src_commitv, 3, "&s", &subject); if (opt_subject) subject = (const char *) opt_subject; g_variant_get_child (src_commitv, 4, "&s", &body); if (opt_body) body = (const char *) opt_body; dst_collection_id = ostree_repo_get_collection_id (dst_repo); collection_ids = g_ptr_array_new_with_free_func (g_free); if (dst_collection_id) { main_collection_id = dst_collection_id; g_ptr_array_add (collection_ids, g_strdup (dst_collection_id)); } if (opt_extra_collection_ids != NULL) { for (j = 0; opt_extra_collection_ids[j] != NULL; j++) { const char *cid = opt_extra_collection_ids[j]; if (main_collection_id == NULL) main_collection_id = cid; /* Fall back to first arg */ if (g_strcmp0 (cid, dst_collection_id) != 0) g_ptr_array_add (collection_ids, g_strdup (cid)); } } g_ptr_array_sort (collection_ids, (GCompareFunc)flatpak_strcmp0_ptr); /* Copy old metadata */ g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); /* Bindings. xa.ref is deprecated but added anyway for backwards compatibility. */ g_variant_builder_add (&metadata_builder, "{sv}", "ostree.collection-binding", g_variant_new_string (main_collection_id ? main_collection_id : "")); if (collection_ids->len > 0) { g_autoptr(GVariantBuilder) cr_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)")); for (j = 0; j < collection_ids->len; j++) g_variant_builder_add (cr_builder, "(ss)", g_ptr_array_index (collection_ids, j), dst_ref); g_variant_builder_add (&metadata_builder, "{sv}", "ostree.collection-refs-binding", g_variant_builder_end (cr_builder)); } g_variant_builder_add (&metadata_builder, "{sv}", "ostree.ref-binding", g_variant_new_strv (&dst_ref, 1)); g_variant_builder_add (&metadata_builder, "{sv}", "xa.ref", g_variant_new_string (dst_ref)); /* Record the source commit. This is nice to have, but it also means the commit-from gets a different commit id, which avoids problems with e.g. sharing .commitmeta files (signatures) */ g_variant_builder_add (&metadata_builder, "{sv}", "xa.from_commit", g_variant_new_string (resolved_ref)); for (j = 0; j < g_variant_n_children (commitv_metadata); j++) { g_autoptr(GVariant) child = g_variant_get_child_value (commitv_metadata, j); g_autoptr(GVariant) keyv = g_variant_get_child_value (child, 0); const char *key = g_variant_get_string (keyv, NULL); if (strcmp (key, "xa.ref") == 0 || strcmp (key, "xa.from_commit") == 0 || strcmp (key, "ostree.collection-binding") == 0 || strcmp (key, "ostree.collection-refs-binding") == 0 || strcmp (key, "ostree.ref-binding") == 0) continue; if (opt_endoflife && strcmp (key, OSTREE_COMMIT_META_KEY_ENDOFLIFE) == 0) continue; g_variant_builder_add_value (&metadata_builder, child); } if (opt_endoflife && *opt_endoflife) g_variant_builder_add (&metadata_builder, "{sv}", OSTREE_COMMIT_META_KEY_ENDOFLIFE, g_variant_new_string (opt_endoflife)); timestamp = ostree_commit_get_timestamp (src_commitv); if (opt_timestamp) timestamp = ts.tv_sec; metadata = g_variant_ref_sink (g_variant_builder_end (&metadata_builder)); if (!ostree_repo_write_commit_with_time (dst_repo, dst_parent, subject, body, metadata, OSTREE_REPO_FILE (dst_root), timestamp, &commit_checksum, cancellable, error)) return FALSE; g_print ("%s: %s\n", dst_ref, commit_checksum); if (!ostree_repo_load_commit (dst_repo, commit_checksum, &dst_commitv, NULL, error)) return FALSE; /* This doesn't copy the detached metadata. I'm not sure if this is a problem. * The main thing there is commit signatures, and we can't copy those, as the commit hash changes. */ if (opt_gpg_key_ids) { char **iter; for (iter = opt_gpg_key_ids; iter && *iter; iter++) { const char *keyid = *iter; g_autoptr(GError) my_error = NULL; if (!ostree_repo_sign_commit (dst_repo, commit_checksum, keyid, opt_gpg_homedir, cancellable, &my_error) && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } } if (dst_collection_id != NULL) { OstreeCollectionRef ref = { (char *) dst_collection_id, (char *) dst_ref }; ostree_repo_transaction_set_collection_ref (dst_repo, &ref, commit_checksum); } else { ostree_repo_transaction_set_ref (dst_repo, NULL, dst_ref, commit_checksum); } if (opt_extra_collection_ids) { for (j = 0; opt_extra_collection_ids[j] != NULL; j++) { OstreeCollectionRef ref = { (char *) opt_extra_collection_ids[j], (char *) dst_ref }; ostree_repo_transaction_set_collection_ref (dst_repo, &ref, commit_checksum); } } /* Copy + Rewrite any deltas */ { const char *from[2]; gsize j, n_from = 0; if (dst_parent != NULL) from[n_from++] = dst_parent; from[n_from++] = NULL; for (j = 0; j < n_from; j++) { g_autoptr(GError) local_error = NULL; if (!rewrite_delta (src_repo, resolved_ref, dst_repo, commit_checksum, dst_commitv, from[j], &local_error)) g_debug ("Failed to copy delta: %s", local_error->message); } } } if (!ostree_repo_commit_transaction (dst_repo, NULL, cancellable, error)) return FALSE; if (opt_update_appstream && !flatpak_repo_generate_appstream (dst_repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, 0, cancellable, error)) return FALSE; if (!opt_no_update_summary && !flatpak_repo_update (dst_repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir, cancellable, error)) return FALSE; return TRUE; }