static gboolean fsck_reachable_objects_from_commits (OtFsckData *data, GHashTable *commits, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GHashTableIter hash_iter; gpointer key, value; ot_lhash GHashTable *reachable_objects = NULL; ot_lobj GInputStream *input = NULL; ot_lobj GFileInfo *file_info = NULL; ot_lvariant GVariant *xattrs = NULL; ot_lvariant GVariant *metadata = NULL; ot_lfree guchar *computed_csum = NULL; ot_lfree char *tmp_checksum = NULL; reachable_objects = ostree_traverse_new_reachable (); g_hash_table_iter_init (&hash_iter, commits); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; ostree_object_name_deserialize (serialized_key, &checksum, &objtype); g_assert (objtype == OSTREE_OBJECT_TYPE_COMMIT); if (!ostree_traverse_commit (data->repo, checksum, 0, reachable_objects, cancellable, error)) goto out; } g_hash_table_iter_init (&hash_iter, reachable_objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; ostree_object_name_deserialize (serialized_key, &checksum, &objtype); g_clear_object (&input); g_clear_object (&file_info); g_clear_pointer (&xattrs, (GDestroyNotify) g_variant_unref); if (objtype == OSTREE_OBJECT_TYPE_COMMIT || objtype == OSTREE_OBJECT_TYPE_DIR_TREE || objtype == OSTREE_OBJECT_TYPE_DIR_META) { g_clear_pointer (&metadata, (GDestroyNotify) g_variant_unref); if (!ostree_repo_load_variant (data->repo, objtype, checksum, &metadata, error)) { g_prefix_error (error, "Loading metadata object %s: ", checksum); goto out; } if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_validate_structureof_commit (metadata, error)) { g_prefix_error (error, "While validating commit metadata '%s': ", checksum); goto out; } } else if (objtype == OSTREE_OBJECT_TYPE_DIR_TREE) { if (!ostree_validate_structureof_dirtree (metadata, error)) { g_prefix_error (error, "While validating directory tree '%s': ", checksum); goto out; } } else if (objtype == OSTREE_OBJECT_TYPE_DIR_META) { if (!ostree_validate_structureof_dirmeta (metadata, error)) { g_prefix_error (error, "While validating directory metadata '%s': ", checksum); goto out; } } else g_assert_not_reached (); input = g_memory_input_stream_new_from_data (g_variant_get_data (metadata), g_variant_get_size (metadata), NULL); } else if (objtype == OSTREE_OBJECT_TYPE_FILE) { guint32 mode; if (!ostree_repo_load_file (data->repo, checksum, &input, &file_info, &xattrs, cancellable, error)) { g_prefix_error (error, "Loading file object %s: ", checksum); goto out; } mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); if (!ostree_validate_structureof_file_mode (mode, error)) { g_prefix_error (error, "While validating file '%s': ", checksum); goto out; } } else { g_assert_not_reached (); } g_free (computed_csum); if (!ostree_checksum_file_from_input (file_info, xattrs, input, objtype, &computed_csum, cancellable, error)) goto out; g_free (tmp_checksum); tmp_checksum = ostree_checksum_from_bytes (computed_csum); if (strcmp (checksum, tmp_checksum) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "corrupted object %s.%s; actual checksum: %s", checksum, ostree_object_type_to_string (objtype), tmp_checksum); goto out; } } ret = TRUE; out: return ret; }
static gboolean checkout_one_file_at (OstreeRepo *repo, OstreeRepoCheckoutOptions *options, GFile *source, GFileInfo *source_info, int destination_dfd, const char *destination_name, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *checksum; gboolean is_symlink; gboolean can_cache; gboolean need_copy = TRUE; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; g_autoptr(GInputStream) input = NULL; g_autoptr(GVariant) xattrs = NULL; gboolean is_whiteout; is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK; checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source); is_whiteout = !is_symlink && options->process_whiteouts && g_str_has_prefix (destination_name, WHITEOUT_PREFIX); /* First, see if it's a Docker whiteout, * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go */ if (is_whiteout) { const char *name = destination_name + (sizeof (WHITEOUT_PREFIX) - 1); if (!name[0]) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid empty whiteout '%s'", name); goto out; } g_assert (name[0] != '/'); /* Sanity */ if (!glnx_shutil_rm_rf_at (destination_dfd, name, cancellable, error)) goto out; need_copy = FALSE; } else if (!is_symlink) { gboolean did_hardlink = FALSE; /* Try to do a hardlink first, if it's a regular file. This also * traverses all parent repos. */ OstreeRepo *current_repo = repo; while (current_repo) { gboolean is_bare = ((current_repo->mode == OSTREE_REPO_MODE_BARE && options->mode == OSTREE_REPO_CHECKOUT_MODE_NONE) || (current_repo->mode == OSTREE_REPO_MODE_BARE_USER && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)); gboolean current_can_cache = (options->enable_uncompressed_cache && current_repo->enable_uncompressed_cache); gboolean is_archive_z2_with_cache = (current_repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER && current_can_cache); /* But only under these conditions */ if (is_bare || is_archive_z2_with_cache) { /* Override repo mode; for archive-z2 we're looking in the cache, which is in "bare" form */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_file_hardlink (current_repo, options, loose_path_buf, destination_dfd, destination_name, TRUE, &did_hardlink, cancellable, error)) goto out; if (did_hardlink && options->devino_to_csum_cache) { struct stat stbuf; OstreeDevIno *key; if (TEMP_FAILURE_RETRY (fstatat (destination_dfd, destination_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0) { glnx_set_error_from_errno (error); goto out; } key = g_new (OstreeDevIno, 1); key->dev = stbuf.st_dev; key->ino = stbuf.st_ino; memcpy (key->checksum, checksum, OSTREE_SHA256_STRING_LEN+1); g_hash_table_add ((GHashTable*)options->devino_to_csum_cache, key); } if (did_hardlink) break; } current_repo = current_repo->parent_repo; } need_copy = !did_hardlink; } can_cache = (options->enable_uncompressed_cache && repo->enable_uncompressed_cache); /* Ok, if we're archive-z2 and we didn't find an object, uncompress * it now, stick it in the cache, and then hardlink to that. */ if (can_cache && !is_whiteout && !is_symlink && need_copy && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) { gboolean did_hardlink; if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL, cancellable, error)) goto out; /* Overwrite any parent repo from earlier */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_object_for_uncompressed_cache (repo, loose_path_buf, source_info, input, cancellable, error)) { g_prefix_error (error, "Unpacking loose object %s: ", checksum); goto out; } g_clear_object (&input); /* Store the 2-byte objdir prefix (e.g. e3) in a set. The basic * idea here is that if we had to unpack an object, it's very * likely we're replacing some other object, so we may need a GC. * * This model ensures that we do work roughly proportional to * the size of the changes. For example, we don't scan any * directories if we didn't modify anything, meaning you can * checkout the same tree multiple times very quickly. * * This is also scale independent; we don't hardcode e.g. looking * at 1000 objects. * * The downside is that if we're unlucky, we may not free * an object for quite some time. */ g_mutex_lock (&repo->cache_lock); { gpointer key = GUINT_TO_POINTER ((g_ascii_xdigit_value (checksum[0]) << 4) + g_ascii_xdigit_value (checksum[1])); if (repo->updated_uncompressed_dirs == NULL) repo->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL); g_hash_table_insert (repo->updated_uncompressed_dirs, key, key); } g_mutex_unlock (&repo->cache_lock); if (!checkout_file_hardlink (repo, options, loose_path_buf, destination_dfd, destination_name, FALSE, &did_hardlink, cancellable, error)) { g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name); goto out; } need_copy = !did_hardlink; } /* Fall back to copy if we couldn't hardlink */ if (need_copy) { if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs, cancellable, error)) goto out; if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES) { if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) { g_prefix_error (error, "Union checkout of %s to %s: ", checksum, destination_name); goto out; } } else { if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) { g_prefix_error (error, "Checkout of %s to %s: ", checksum, destination_name); goto out; } } if (input) { if (!g_input_stream_close (input, cancellable, error)) goto out; } } ret = TRUE; out: return ret; }
gboolean ostree_builtin_show (int argc, char **argv, OstreeRepo *repo, GCancellable *cancellable, GError **error) { GOptionContext *context; gboolean ret = FALSE; const char *rev; gs_free char *resolved_rev = NULL; context = g_option_context_new ("OBJECT - Output a metadata object"); g_option_context_add_main_entries (context, options, NULL); if (!g_option_context_parse (context, &argc, &argv, error)) goto out; if (argc <= 1) { ot_util_usage_error (context, "An object argument is required", error); goto out; } rev = argv[1]; 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)) goto out; if (!do_print_metadata_key (repo, resolved_rev, detached, key, error)) goto out; } else if (opt_print_related) { if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) goto out; if (!do_print_related (repo, rev, resolved_rev, error)) goto out; } else if (opt_print_variant_type) { if (!do_print_variant_generic (G_VARIANT_TYPE (opt_print_variant_type), rev, error)) goto out; } else { gboolean found = FALSE; if (!ostree_validate_checksum_string (rev, NULL)) { if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error)) goto out; if (!print_object (repo, OSTREE_OBJECT_TYPE_COMMIT, resolved_rev, error)) goto out; } else { if (!print_if_found (repo, OSTREE_OBJECT_TYPE_COMMIT, rev, &found, cancellable, error)) goto out; if (!print_if_found (repo, OSTREE_OBJECT_TYPE_DIR_META, rev, &found, cancellable, error)) goto out; if (!print_if_found (repo, OSTREE_OBJECT_TYPE_DIR_TREE, rev, &found, cancellable, error)) goto out; if (!found) { gs_unref_object GFileInfo *finfo = NULL; gs_unref_variant GVariant *xattrs = NULL; GFileType filetype; if (!ostree_repo_load_file (repo, resolved_rev, NULL, &finfo, &xattrs, cancellable, error)) goto out; g_print ("Object: %s\nType: %s\n", rev, ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE)); 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) { gs_free char *xattr_string = g_variant_print (xattrs, TRUE); g_print ("{ %s }\n", xattr_string); } else { g_print ("(none)\n"); } } } } ret = TRUE; out: if (context) g_option_context_free (context); return ret; }
static gboolean load_and_fsck_one_object (OstreeRepo *repo, const char *checksum, OstreeObjectType objtype, gboolean *out_found_corruption, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gboolean missing = FALSE; gs_unref_variant GVariant *metadata = NULL; gs_unref_object GInputStream *input = NULL; gs_unref_object GFileInfo *file_info = NULL; gs_unref_variant GVariant *xattrs = NULL; GError *temp_error = NULL; if (OSTREE_OBJECT_TYPE_IS_META (objtype)) { if (!ostree_repo_load_variant (repo, objtype, checksum, &metadata, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_printerr ("Object missing: %s.%s\n", checksum, ostree_object_type_to_string (objtype)); missing = TRUE; } else { g_prefix_error (error, "Loading metadata object %s: ", checksum); goto out; } } else { if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_validate_structureof_commit (metadata, error)) { g_prefix_error (error, "While validating commit metadata '%s': ", checksum); goto out; } } else if (objtype == OSTREE_OBJECT_TYPE_DIR_TREE) { if (!ostree_validate_structureof_dirtree (metadata, error)) { g_prefix_error (error, "While validating directory tree '%s': ", checksum); goto out; } } else if (objtype == OSTREE_OBJECT_TYPE_DIR_META) { if (!ostree_validate_structureof_dirmeta (metadata, error)) { g_prefix_error (error, "While validating directory metadata '%s': ", checksum); goto out; } } input = g_memory_input_stream_new_from_data (g_variant_get_data (metadata), g_variant_get_size (metadata), NULL); } } else { guint32 mode; g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); if (!ostree_repo_load_file (repo, checksum, &input, &file_info, &xattrs, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_printerr ("Object missing: %s.%s\n", checksum, ostree_object_type_to_string (objtype)); missing = TRUE; } else { g_prefix_error (error, "Loading file object %s: ", checksum); goto out; } } else { mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); if (!ostree_validate_structureof_file_mode (mode, error)) { g_prefix_error (error, "While validating file '%s': ", checksum); goto out; } } } if (missing) { *out_found_corruption = TRUE; } else { gs_free guchar *computed_csum = NULL; gs_free char *tmp_checksum = NULL; if (!ostree_checksum_file_from_input (file_info, xattrs, input, objtype, &computed_csum, cancellable, error)) goto out; tmp_checksum = ostree_checksum_from_bytes (computed_csum); if (strcmp (checksum, tmp_checksum) != 0) { gs_free char *msg = g_strdup_printf ("corrupted object %s.%s; actual checksum: %s", checksum, ostree_object_type_to_string (objtype), tmp_checksum); if (opt_delete) { g_printerr ("%s\n", msg); (void) ostree_repo_delete_object (repo, objtype, checksum, cancellable, NULL); *out_found_corruption = TRUE; } else { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, msg); goto out; } } } ret = TRUE; out: return ret; }
static gboolean build_content_sizenames_recurse (OstreeRepo *repo, OstreeRepoCommitTraverseIter *iter, GHashTable *sizenames_map, GHashTable *include_only_objects, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; while (TRUE) { OstreeRepoCommitIterResult iterres = ostree_repo_commit_traverse_iter_next (iter, cancellable, error); if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_ERROR) goto out; else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_END) break; else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_FILE) { char *name; char *checksum; OstreeDeltaContentSizeNames *csizenames; ostree_repo_commit_traverse_iter_get_file (iter, &name, &checksum); if (include_only_objects && !g_hash_table_contains (include_only_objects, checksum)) continue; csizenames = g_hash_table_lookup (sizenames_map, checksum); if (!csizenames) { g_autoptr(GFileInfo) finfo = NULL; if (!ostree_repo_load_file (repo, checksum, NULL, &finfo, NULL, cancellable, error)) goto out; if (g_file_info_get_file_type (finfo) != G_FILE_TYPE_REGULAR) continue; csizenames = g_new0 (OstreeDeltaContentSizeNames, 1); csizenames->checksum = g_strdup (checksum); csizenames->size = g_file_info_get_size (finfo); g_hash_table_replace (sizenames_map, csizenames->checksum, csizenames); } if (!csizenames->basenames) csizenames->basenames = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (csizenames->basenames, g_strdup (name)); } else if (iterres == OSTREE_REPO_COMMIT_ITER_RESULT_DIR) { char *name; char *content_checksum; char *meta_checksum; g_autoptr(GVariant) dirtree = NULL; ostree_cleanup_repo_commit_traverse_iter OstreeRepoCommitTraverseIter subiter = { 0, }; ostree_repo_commit_traverse_iter_get_dir (iter, &name, &content_checksum, &meta_checksum); if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_DIR_TREE, content_checksum, &dirtree, error)) goto out; if (!ostree_repo_commit_traverse_iter_init_dirtree (&subiter, repo, dirtree, OSTREE_REPO_COMMIT_TRAVERSE_FLAG_NONE, error)) goto out; if (!build_content_sizenames_recurse (repo, &subiter, sizenames_map, include_only_objects, cancellable, error)) goto out; } else g_assert_not_reached (); } ret = TRUE; out: return ret; }