/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2017 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: * Matthias Clasen */ #include "config.h" #include #include #include #include #include #include "libglnx.h" #include "flatpak-builtins.h" #include "flatpak-utils-private.h" #include "flatpak-table-printer.h" #include "flatpak-variant-impl-private.h" static gboolean opt_info; static gboolean opt_branches; static gboolean opt_subsets; static gchar *opt_metadata_branch; static gchar *opt_commits_branch; static gchar *opt_subset; static gboolean ostree_repo_mode_to_string (OstreeRepoMode mode, const char **out_mode, GError **error) { const char *ret_mode; switch (mode) { case OSTREE_REPO_MODE_BARE: ret_mode = "bare"; break; case OSTREE_REPO_MODE_BARE_USER: ret_mode = "bare-user"; break; case OSTREE_REPO_MODE_BARE_USER_ONLY: ret_mode = "bare-user-only"; break; case OSTREE_REPO_MODE_ARCHIVE: /* Legacy alias */ ret_mode = "archive-z2"; break; default: return glnx_throw (error, "Invalid mode '%d'", mode); } *out_mode = ret_mode; return TRUE; } static void print_info (OstreeRepo *repo, GVariant *index, GVariant *summary) { const char *title; const char *comment; const char *description; const char *homepage; const char *icon; const char *collection_id; const char *default_branch; const char *redirect_url; const char *deploy_collection_id; const char *authenticator_name; gboolean authenticator_install = FALSE; g_autoptr(GVariant) gpg_keys = NULL; OstreeRepoMode mode; const char *mode_string = "unknown"; g_autoptr(GVariant) meta = NULL; g_autoptr(GVariant) refs = NULL; guint cache_version = 0; gboolean indexed_deltas = FALSE; mode = ostree_repo_get_mode (repo); ostree_repo_mode_to_string (mode, &mode_string, NULL); g_print (_("Repo mode: %s\n"), mode_string); if (index) meta = g_variant_get_child_value (index, 1); else meta = g_variant_get_child_value (summary, 1); g_print (_("Indexed summaries: %s\n"), index != NULL ? _("true") : _("false")); if (index) { VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index); VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref); gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries); g_print (_("Subsummaries: ")); for (gsize i = 0; i < n_subsummaries; i++) { VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i); const char *name = var_summary_index_subsummaries_entry_get_key (entry); if (i != 0) g_print (", "); g_print ("%s", name); } g_print ("\n"); } g_variant_lookup (meta, "xa.cache-version", "u", &cache_version); g_print (_("Cache version: %d\n"), cache_version); g_variant_lookup (meta, "ostree.summary.indexed-deltas", "b", &indexed_deltas); g_print (_("Indexed deltas: %s\n"), indexed_deltas ? _("true") : _("false")); if (g_variant_lookup (meta, "xa.title", "&s", &title)) g_print (_("Title: %s\n"), title); if (g_variant_lookup (meta, "xa.comment", "&s", &comment)) g_print (_("Comment: %s\n"), comment); if (g_variant_lookup (meta, "xa.description", "&s", &description)) g_print (_("Description: %s\n"), description); if (g_variant_lookup (meta, "xa.homepage", "&s", &homepage)) g_print (_("Homepage: %s\n"), homepage); if (g_variant_lookup (meta, "xa.icon", "&s", &icon)) g_print (_("Icon: %s\n"), icon); if (g_variant_lookup (meta, "collection-id", "&s", &collection_id)) g_print (_("Collection ID: %s\n"), collection_id); if (g_variant_lookup (meta, "xa.default-branch", "&s", &default_branch)) g_print (_("Default branch: %s\n"), default_branch); if (g_variant_lookup (meta, "xa.redirect-url", "&s", &redirect_url)) g_print (_("Redirect URL: %s\n"), redirect_url); if (g_variant_lookup (meta, OSTREE_META_KEY_DEPLOY_COLLECTION_ID, "&s", &deploy_collection_id)) g_print (_("Deploy collection ID: %s\n"), deploy_collection_id); if (g_variant_lookup (meta, "xa.authenticator-name", "&s", &authenticator_name)) g_print (_("Authenticator name: %s\n"), authenticator_name); if (g_variant_lookup (meta, "xa.authenticator-install", "&s", &authenticator_install)) g_print (_("Authenticator install: %s\n"), authenticator_install ? _("true") : _("false")); if ((gpg_keys = g_variant_lookup_value (meta, "xa.gpg-keys", G_VARIANT_TYPE_BYTESTRING)) != NULL) { const guchar *gpg_data = g_variant_get_data (gpg_keys); gsize gpg_size = g_variant_get_size (gpg_keys); g_autofree gchar *gpg_data_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, gpg_data, gpg_size); g_print (_("GPG key hash: %s\n"), gpg_data_checksum); } refs = g_variant_get_child_value (summary, 0); g_print (_("%zd summary branches\n"), g_variant_n_children (refs)); } static void print_branches_for_subsummary (FlatpakTablePrinter *printer, const char *subsummary, GVariant *summary) { g_autoptr(GVariant) meta = NULL; guint summary_version = 0; g_autofree char *subset = NULL; if (subsummary != NULL) { const char *dash = strrchr (subsummary, '-'); if (dash) subset = g_strndup (subsummary, dash - subsummary); } if (opt_subset != NULL) { if (subset == NULL || strcmp (subset, opt_subset) != 0) return; /* Not the requested subset, ignore */ } meta = g_variant_get_child_value (summary, 1); g_variant_lookup (meta, "xa.summary-version", "u", &summary_version); if (summary_version == 1) { g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0); GVariantIter iter; const char *ref; GVariant *refdata_iter = NULL; g_variant_iter_init (&iter, refs); while (g_variant_iter_next (&iter, "(&s@(taya{sv}))", &ref, &refdata_iter)) { g_autoptr(GVariant) refdata = refdata_iter; g_autoptr(GVariant) ref_meta = g_variant_get_child_value (refdata, 2); g_autoptr(GVariant) data = g_variant_lookup_value (ref_meta, "xa.data", NULL); guint64 installed_size; guint64 download_size; const char *metadata; const char *eol; if (data == NULL) continue; int old_row = flatpak_table_printer_lookup_row (printer, ref); if (old_row >= 0) { if (subset) flatpak_table_printer_append_cell_with_comma_unique (printer, old_row, 3, subset); continue; } g_variant_get (data, "(tt&s)", &installed_size, &download_size, &metadata); g_autofree char *installed = g_format_size (GUINT64_FROM_BE (installed_size)); g_autofree char *download = g_format_size (GUINT64_FROM_BE (download_size)); flatpak_table_printer_set_key (printer, ref); flatpak_table_printer_add_column (printer, ref); flatpak_table_printer_add_decimal_column (printer, installed); flatpak_table_printer_add_decimal_column (printer, download); /* Subset */ flatpak_table_printer_add_column (printer, subset); flatpak_table_printer_add_column (printer, ""); /* Options */ if (g_variant_lookup (ref_meta, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, "&s", &eol)) flatpak_table_printer_append_with_comma_printf (printer, "eol=%s", eol); if (g_variant_lookup (ref_meta, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, "&s", &eol)) flatpak_table_printer_append_with_comma_printf (printer, "eol-rebase=%s", eol); flatpak_table_printer_finish_row (printer); } } else { g_autoptr(GVariant) cache = NULL; g_autoptr(GVariant) sparse_cache = NULL; g_variant_lookup (meta, "xa.sparse-cache", "@a{sa{sv}}", &sparse_cache); cache = g_variant_lookup_value (meta, "xa.cache", NULL); if (cache) { g_autoptr(GVariant) refdata = NULL; GVariantIter iter; const char *ref; guint64 installed_size; guint64 download_size; const char *metadata; refdata = g_variant_get_variant (cache); g_variant_iter_init (&iter, refdata); while (g_variant_iter_next (&iter, "{&s(tt&s)}", &ref, &installed_size, &download_size, &metadata)) { g_autofree char *installed = g_format_size (GUINT64_FROM_BE (installed_size)); g_autofree char *download = g_format_size (GUINT64_FROM_BE (download_size)); int old_row = flatpak_table_printer_lookup_row (printer, ref); if (old_row >= 0) { if (subset) flatpak_table_printer_append_cell_with_comma_unique (printer, old_row, 3, subset); continue; } flatpak_table_printer_set_key (printer, ref); flatpak_table_printer_add_column (printer, ref); flatpak_table_printer_add_decimal_column (printer, installed); flatpak_table_printer_add_decimal_column (printer, download); flatpak_table_printer_add_column (printer, subset); flatpak_table_printer_add_column (printer, ""); /* Options */ if (sparse_cache) { g_autoptr(GVariant) sparse = NULL; if (g_variant_lookup (sparse_cache, ref, "@a{sv}", &sparse)) { const char *eol; if (g_variant_lookup (sparse, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, "&s", &eol)) flatpak_table_printer_append_with_comma_printf (printer, "eol=%s", eol); if (g_variant_lookup (sparse, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, "&s", &eol)) flatpak_table_printer_append_with_comma_printf (printer, "eol-rebase=%s", eol); } } flatpak_table_printer_finish_row (printer); } } } } static void print_branches (OstreeRepo *repo, GVariant *index, GVariant *summary) { g_autoptr(FlatpakTablePrinter) printer = NULL; printer = flatpak_table_printer_new (); flatpak_table_printer_set_column_title (printer, 0, _("Ref")); flatpak_table_printer_set_column_title (printer, 1, _("Installed")); flatpak_table_printer_set_column_title (printer, 2, _("Download")); flatpak_table_printer_set_column_title (printer, 3, _("Subsets")); flatpak_table_printer_set_column_title (printer, 4, _("Options")); if (index != NULL) { VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index); VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref); gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries); for (gsize i = 0; i < n_subsummaries; i++) { VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i); const char *name = var_summary_index_subsummaries_entry_get_key (entry); VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry); gsize checksum_bytes_len; const guchar *checksum_bytes; g_autofree char *digest = NULL; g_autoptr(GVariant) subsummary_v = NULL; g_autoptr(GError) error = NULL; checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len); if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN)) { g_printerr ("Invalid checksum for digested summary\n"); continue; } digest = ostree_checksum_from_bytes (checksum_bytes); subsummary_v = flatpak_repo_load_digested_summary (repo, digest, &error); if (subsummary_v == NULL) { g_printerr ("Failed to load subsummary %s (digest %s)\n", name, digest); continue; } print_branches_for_subsummary (printer, name, subsummary_v); } } else print_branches_for_subsummary (printer, NULL, summary); flatpak_table_printer_sort (printer, (GCompareFunc) strcmp); flatpak_table_printer_print (printer); } static void print_subsets (OstreeRepo *repo, GVariant *index) { g_autoptr(FlatpakTablePrinter) printer = NULL; printer = flatpak_table_printer_new (); flatpak_table_printer_set_column_title (printer, 0, _("Subset")); flatpak_table_printer_set_column_title (printer, 1, _("Digest")); flatpak_table_printer_set_column_title (printer, 2, _("History length")); if (index != NULL) { VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index); VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref); gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries); for (gsize i = 0; i < n_subsummaries; i++) { VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i); const char *name = var_summary_index_subsummaries_entry_get_key (entry); VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry); gsize checksum_bytes_len; const guchar *checksum_bytes; g_autofree char *digest = NULL; VarArrayofChecksumRef history = var_subsummary_get_history (subsummary); gsize history_len = var_arrayof_checksum_get_length (history); if (opt_subset != NULL && !g_str_has_prefix (name, opt_subset)) continue; checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len); if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN)) { g_printerr ("Invalid checksum for digested summary\n"); continue; } digest = ostree_checksum_from_bytes (checksum_bytes); flatpak_table_printer_add_column (printer, name); flatpak_table_printer_add_column (printer, digest); flatpak_table_printer_take_column (printer, g_strdup_printf ("%"G_GSIZE_FORMAT, history_len)); flatpak_table_printer_finish_row (printer); } } flatpak_table_printer_print (printer); } static void print_metadata (OstreeRepo *repo, GVariant *index, GVariant *summary, const char *branch) { g_autoptr(GVariant) meta = NULL; guint summary_version = 0; guint64 installed_size; guint64 download_size; const char *metadata; GVariantIter iter; const char *ref; g_autoptr(GVariant) subsummary_v = NULL; if (index) { g_autofree char *arch = flatpak_get_arch_for_ref (branch); if (arch != NULL) { VarSummaryIndexRef index_ref = var_summary_index_from_gvariant (index); VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index_ref); gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries); for (gsize i = 0; i < n_subsummaries; i++) { VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i); const char *name = var_summary_index_subsummaries_entry_get_key (entry); VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry); gsize checksum_bytes_len; const guchar *checksum_bytes; if (strcmp (name, arch) == 0) { g_autofree char *digest = NULL; g_autoptr(GError) error = NULL; checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len); if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN)) break; digest = ostree_checksum_from_bytes (checksum_bytes); subsummary_v = flatpak_repo_load_digested_summary (repo, digest, &error); if (subsummary_v == NULL) g_printerr ("Failed to load subsummary %s (digest %s)\n", name, digest); break; } } } } if (subsummary_v) summary = subsummary_v; meta = g_variant_get_child_value (summary, 1); g_variant_lookup (meta, "xa.summary-version", "u", &summary_version); if (summary_version == 1) { g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0); GVariant *refdata_iter = NULL; g_variant_iter_init (&iter, refs); while (g_variant_iter_next (&iter, "(&s@(taya{sv}))", &ref, &refdata_iter)) { g_autoptr(GVariant) refdata = refdata_iter; g_autoptr(GVariant) ref_meta = g_variant_get_child_value (refdata, 2); if (strcmp (branch, ref) == 0) { g_autoptr(GVariant) data = g_variant_lookup_value (ref_meta, "xa.data", NULL); if (data) { g_variant_get (data, "(tt&s)", &installed_size, &download_size, &metadata); g_print ("%s\n", metadata); break; } } } } else /* Version 0 */ { g_autoptr(GVariant) cache = g_variant_lookup_value (meta, "xa.cache", NULL); if (cache) { g_autoptr(GVariant) refdata = g_variant_get_variant (cache); g_variant_iter_init (&iter, refdata); while (g_variant_iter_next (&iter, "{&s(tt&s)}", &ref, &installed_size, &download_size, &metadata)) { if (strcmp (branch, ref) == 0) { g_print ("%s\n", metadata); break; } } } } } static void dump_indented_lines (const gchar *data) { const char * indent = " "; const gchar *pos; for (;;) { pos = strchr (data, '\n'); if (pos) { g_print ("%s%.*s", indent, (int) (pos + 1 - data), data); data = pos + 1; } else { if (data[0] != '\0') g_print ("%s%s\n", indent, data); break; } } } static void dump_deltas_for_commit (GPtrArray *deltas, const char *checksum) { int i; gboolean header_printed = FALSE; if (!deltas) return; for (i = 0; i < deltas->len; i++) { const char *delta = g_ptr_array_index (deltas, i); if (g_str_equal (delta, checksum)) { if (!header_printed) { g_print ("Static Deltas:\n"); header_printed = TRUE; } g_print (" from scratch\n"); } else if (strchr (delta, '-')) { g_auto(GStrv) parts = g_strsplit (delta, "-", 0); if (g_str_equal (parts[1], checksum)) { if (!header_printed) { g_print ("Static Deltas:\n"); header_printed = TRUE; } g_print (" from %s\n", parts[0]); } } } if (header_printed) g_print ("\n"); } static gboolean dump_commit (const char *commit, GVariant *variant, GPtrArray *deltas, GError **error) { const gchar *subject; const gchar *body; guint64 timestamp; g_autofree char *str = NULL; /* See OSTREE_COMMIT_GVARIANT_FORMAT */ g_variant_get (variant, "(a{sv}aya(say)&s&stayay)", NULL, NULL, NULL, &subject, &body, ×tamp, NULL, NULL); timestamp = GUINT64_FROM_BE (timestamp); str = format_timestamp (timestamp); g_print ("Commit: %s\n", commit); g_print ("Date: %s\n", str); if (subject[0]) { g_print ("\n"); dump_indented_lines (subject); } else { g_print ("(no subject)\n"); } if (body[0]) { g_print ("\n"); dump_indented_lines (body); } g_print ("\n"); dump_deltas_for_commit (deltas, commit); return TRUE; } static gboolean log_commit (OstreeRepo *repo, const char *checksum, gboolean is_recurse, GPtrArray *deltas, GError **error) { g_autoptr(GVariant) variant = NULL; g_autofree char *parent = NULL; gboolean ret = FALSE; GError *local_error = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, &local_error)) { if (is_recurse && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_print ("<< History beyond this commit not fetched >>\n"); g_clear_error (&local_error); ret = TRUE; } else { g_propagate_error (error, local_error); } goto out; } if (!dump_commit (checksum, variant, deltas, error)) goto out; /* Get the parent of this commit */ parent = ostree_commit_get_parent (variant); if (parent && !log_commit (repo, parent, TRUE, deltas, error)) goto out; ret = TRUE; out: return ret; } static gboolean print_commits (OstreeRepo *repo, const char *collection_id, const char *ref, GCancellable *cancellable, GError **error) { g_autofree char *checksum = NULL; g_autoptr(GPtrArray) deltas = NULL; if (!ostree_repo_list_static_delta_names (repo, &deltas, NULL, error)) return FALSE; if (!flatpak_repo_resolve_rev (repo, collection_id, NULL, ref, FALSE, &checksum, cancellable, error)) return FALSE; if (!log_commit (repo, checksum, FALSE, deltas, error)) return FALSE; return TRUE; } static GOptionEntry options[] = { { "info", 0, 0, G_OPTION_ARG_NONE, &opt_info, N_("Print general information about the repository"), NULL }, { "branches", 0, 0, G_OPTION_ARG_NONE, &opt_branches, N_("List the branches in the repository"), NULL }, { "metadata", 0, 0, G_OPTION_ARG_STRING, &opt_metadata_branch, N_("Print metadata for a branch"), N_("BRANCH") }, { "commits", 0, 0, G_OPTION_ARG_STRING, &opt_commits_branch, N_("Show commits for a branch"), N_("BRANCH") }, { "subsets", 0, 0, G_OPTION_ARG_NONE, &opt_subsets, N_("Print information about the repo subsets"), NULL }, { "subset", 0, 0, G_OPTION_ARG_STRING, &opt_subset, N_("Limit information to subsets with this prefix"), NULL }, { NULL } }; gboolean flatpak_builtin_repo (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GFile) location = NULL; g_autoptr(OstreeRepo) repo = NULL; g_autoptr(GVariant) index = NULL; g_autoptr(GVariant) summary = NULL; const char *collection_id; context = g_option_context_new (_("LOCATION - Repository maintenance")); 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, _("LOCATION must be specified"), error); location = g_file_new_for_commandline_arg (argv[1]); repo = ostree_repo_new (location); if (!ostree_repo_open (repo, cancellable, error)) return FALSE; collection_id = ostree_repo_get_collection_id (repo); index = flatpak_repo_load_summary_index (repo, NULL); summary = flatpak_repo_load_summary (repo, error); if (summary == NULL) { g_prefix_error (error, "Error getting repository metadata from summary file: "); return FALSE; } if (!opt_info && !opt_branches && !opt_metadata_branch && !opt_commits_branch && !opt_subsets) opt_info = TRUE; /* Print out the metadata. */ if (opt_info) print_info (repo, index, summary); if (opt_branches) print_branches (repo, index, summary); if (opt_metadata_branch) print_metadata (repo, index, summary, opt_metadata_branch); if (opt_subsets) print_subsets (repo, index); if (opt_commits_branch) { if (!print_commits (repo, collection_id, opt_commits_branch, cancellable, error)) return FALSE; } return TRUE; } gboolean flatpak_complete_repo (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; } return TRUE; }