/* * Copyright (C) 2011 Colin Walters * * SPDX-License-Identifier: LGPL-2.0+ * * This library 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 . * * Author: Colin Walters */ #include "config.h" #include "ot-main.h" #include "ot-builtins.h" #include "ot-dump.h" #include "ostree.h" #include "otutil.h" static gboolean opt_print_related; static char* opt_print_variant_type; static char* opt_print_metadata_key; static char* opt_print_detached_metadata_key; static gboolean opt_print_sizes; static gboolean opt_raw; static gboolean opt_no_byteswap; static char *opt_gpg_homedir; static char *opt_gpg_verify_remote; /* ATTENTION: * Please remember to update the bash-completion script (bash/ostree) and * man page (man/ostree-show.xml) when changing the option list. */ static GOptionEntry options[] = { { "print-related", 0, 0, G_OPTION_ARG_NONE, &opt_print_related, "Show the \"related\" commits", NULL }, { "print-variant-type", 0, 0, G_OPTION_ARG_STRING, &opt_print_variant_type, "Memory map OBJECT (in this case a filename) to the GVariant type string", "TYPE" }, { "print-metadata-key", 0, 0, G_OPTION_ARG_STRING, &opt_print_metadata_key, "Print string value of metadata key", "KEY" }, { "print-detached-metadata-key", 0, 0, G_OPTION_ARG_STRING, &opt_print_detached_metadata_key, "Print string value of detached metadata key", "KEY" }, { "print-sizes", 0, 0, G_OPTION_ARG_NONE, &opt_print_sizes, "Show the commit size metadata", NULL }, { "raw", 0, 0, G_OPTION_ARG_NONE, &opt_raw, "Show raw variant data" }, { "no-byteswap", 'B', 0, G_OPTION_ARG_NONE, &opt_no_byteswap, "Do not automatically convert variant data from big endian" }, { "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"}, { "gpg-verify-remote", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_verify_remote, "Use REMOTE name for GPG configuration", "REMOTE"}, { NULL } }; static gboolean do_print_variant_generic (const GVariantType *type, const char *filename, GError **error) { g_autoptr(GVariant) variant = NULL; glnx_autofd int fd = -1; if (!glnx_openat_rdonly (AT_FDCWD, filename, TRUE, &fd, error)) return FALSE; if (!ot_variant_read_fd (fd, 0, type, FALSE, &variant, error)) return FALSE; ot_dump_variant (variant); return TRUE; } static gboolean do_print_related (OstreeRepo *repo, const char *rev, const char *resolved_rev, GError **error) { g_autoptr(GVariant) variant = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, resolved_rev, &variant, error)) return FALSE; /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ g_autoptr(GVariant) related = g_variant_get_child_value (variant, 2); g_autoptr(GVariantIter) viter = g_variant_iter_new (related); const char *name; GVariant* csum_v; while (g_variant_iter_loop (viter, "(&s@ay)", &name, &csum_v)) { g_autofree char *checksum = ostree_checksum_from_bytes_v (csum_v); g_print ("%s %s\n", name, checksum); } return TRUE; } static gboolean do_print_metadata_key (OstreeRepo *repo, const char *resolved_rev, gboolean detached, const char *key, GError **error) { g_autoptr(GVariant) commit = NULL; g_autoptr(GVariant) metadata = NULL; if (!detached) { if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, resolved_rev, &commit, error)) return FALSE; /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ metadata = g_variant_get_child_value (commit, 0); } else { if (!ostree_repo_read_commit_detached_metadata (repo, resolved_rev, &metadata, NULL, error)) return FALSE; if (metadata == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No detached metadata for commit %s", resolved_rev); return FALSE; } } g_autoptr(GVariant) value = g_variant_lookup_value (metadata, key, NULL); if (!value) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No such metadata key '%s'", key); return FALSE; } if (opt_no_byteswap) { g_autofree char *formatted = g_variant_print (value, TRUE); g_print ("%s\n", formatted); } else ot_dump_variant (value); return TRUE; } static gboolean do_print_sizes (OstreeRepo *repo, const char *rev, GError **error) { g_autoptr(GVariant) commit = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, rev, &commit, error)) { g_prefix_error (error, "Failed to read commit: "); return FALSE; } g_autoptr(GPtrArray) sizes = NULL; if (!ostree_commit_get_object_sizes (commit, &sizes, error)) return FALSE; gint64 new_archived = 0; gint64 new_unpacked = 0; gsize new_objects = 0; gint64 archived = 0; gint64 unpacked = 0; gsize objects = 0; for (guint i = 0; i < sizes->len; i++) { OstreeCommitSizesEntry *entry = sizes->pdata[i]; archived += entry->archived; unpacked += entry->unpacked; objects++; gboolean exists; if (!ostree_repo_has_object (repo, entry->objtype, entry->checksum, &exists, NULL, error)) return FALSE; if (!exists) { /* Object not in local repo */ new_archived += entry->archived; new_unpacked += entry->unpacked; new_objects++; } } g_autofree char *new_archived_str = g_format_size (new_archived); g_autofree char *archived_str = g_format_size (archived); g_autofree char *new_unpacked_str = g_format_size (new_unpacked); g_autofree char *unpacked_str = g_format_size (unpacked); g_print ("Compressed size (needed/total): %s/%s\n" "Unpacked size (needed/total): %s/%s\n" "Number of objects (needed/total): %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "\n", new_archived_str, archived_str, new_unpacked_str, unpacked_str, new_objects, objects); return TRUE; } static gboolean print_object (OstreeRepo *repo, OstreeObjectType objtype, const char *checksum, GError **error) { OstreeDumpFlags flags = OSTREE_DUMP_NONE; g_autoptr(GVariant) variant = NULL; if (!ostree_repo_load_variant (repo, objtype, checksum, &variant, error)) return FALSE; if (opt_raw) flags |= OSTREE_DUMP_RAW; if (opt_no_byteswap) flags |= OSTREE_DUMP_UNSWAPPED; ot_dump_object (objtype, checksum, variant, flags); #ifndef OSTREE_DISABLE_GPGME if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { g_autoptr(OstreeGpgVerifyResult) result = NULL; g_autoptr(GError) local_error = NULL; g_autoptr(GFile) gpg_homedir = opt_gpg_homedir ? g_file_new_for_path (opt_gpg_homedir) : NULL; if (opt_gpg_verify_remote) { result = ostree_repo_verify_commit_for_remote (repo, checksum, opt_gpg_verify_remote, NULL, &local_error); } else { result = ostree_repo_verify_commit_ext (repo, checksum, gpg_homedir, NULL, NULL, &local_error); } if (g_error_matches (local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE)) { /* Ignore */ } else if (local_error != NULL) { g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } else { guint n_sigs = ostree_gpg_verify_result_count_all (result); g_print ("Found %u signature%s:\n", n_sigs, n_sigs == 1 ? "" : "s"); g_autoptr(GString) buffer = g_string_sized_new (256); for (guint ii = 0; ii < n_sigs; ii++) { g_string_append_c (buffer, '\n'); ostree_gpg_verify_result_describe (result, ii, buffer, " ", OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); } g_print ("%s", buffer->str); } } #endif /* OSTREE_DISABLE_GPGME */ return TRUE; } static gboolean print_if_found (OstreeRepo *repo, OstreeObjectType objtype, const char *checksum, gboolean *inout_was_found, GCancellable *cancellable, GError **error) { gboolean have_object = FALSE; if (*inout_was_found) return TRUE; if (!ostree_repo_has_object (repo, objtype, checksum, &have_object, cancellable, error)) return FALSE; if (have_object) { if (!print_object (repo, objtype, checksum, error)) return FALSE; *inout_was_found = TRUE; } return TRUE; } gboolean ostree_builtin_show (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new ("OBJECT"); g_autoptr(OstreeRepo) repo = NULL; if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (argc <= 1) { ot_util_usage_error (context, "An object argument is required", error); return FALSE; } const char *rev = argv[1]; g_autofree char *resolved_rev = NULL; if (opt_print_metadata_key || opt_print_detached_metadata_key) { gboolean detached = opt_print_detached_metadata_key != NULL; const char *key = detached ? opt_print_detached_metadata_key : opt_print_metadata_key; if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) return FALSE; if (!do_print_metadata_key (repo, resolved_rev, detached, key, error)) return FALSE; } else if (opt_print_related) { if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) return FALSE; if (!do_print_related (repo, rev, resolved_rev, error)) return FALSE; } else if (opt_print_variant_type) { if (!do_print_variant_generic (G_VARIANT_TYPE (opt_print_variant_type), rev, error)) return FALSE; } else if (opt_print_sizes) { if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) return FALSE; if (!do_print_sizes (repo, resolved_rev, error)) return FALSE; } else { gboolean found = FALSE; if (!ostree_validate_checksum_string (rev, NULL)) { if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) return FALSE; if (!print_object (repo, OSTREE_OBJECT_TYPE_COMMIT, resolved_rev, error)) return FALSE; } else { if (!print_if_found (repo, OSTREE_OBJECT_TYPE_COMMIT, rev, &found, cancellable, error)) return FALSE; if (!print_if_found (repo, OSTREE_OBJECT_TYPE_DIR_META, rev, &found, cancellable, error)) return FALSE; if (!print_if_found (repo, OSTREE_OBJECT_TYPE_DIR_TREE, rev, &found, cancellable, error)) return FALSE; if (!found) { g_autoptr(GFileInfo) finfo = NULL; g_autoptr(GVariant) xattrs = NULL; if (!ostree_repo_load_file (repo, rev, NULL, &finfo, &xattrs, cancellable, error)) return FALSE; g_print ("Object: %s\nType: %s\n", rev, ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE)); GFileType filetype = g_file_info_get_file_type (finfo); g_print ("File Type: "); switch (filetype) { case G_FILE_TYPE_REGULAR: g_print ("regular\n"); g_print ("Size: %" G_GUINT64_FORMAT "\n", g_file_info_get_size (finfo)); break; case G_FILE_TYPE_SYMBOLIC_LINK: g_print ("symlink\n"); g_print ("Target: %s\n", g_file_info_get_symlink_target (finfo)); break; default: g_printerr ("(unknown type %u)\n", (guint)filetype); } g_print ("Mode: 0%04o\n", g_file_info_get_attribute_uint32 (finfo, "unix::mode")); g_print ("Uid: %u\n", g_file_info_get_attribute_uint32 (finfo, "unix::uid")); g_print ("Gid: %u\n", g_file_info_get_attribute_uint32 (finfo, "unix::gid")); g_print ("Extended Attributes: "); if (xattrs) { g_autofree char *xattr_string = g_variant_print (xattrs, TRUE); g_print ("{ %s }\n", xattr_string); } else { g_print ("(none)\n"); } } } } return TRUE; }