| #include "git-compat-util.h" | |
| #include "commit.h" | |
| #include "gettext.h" | |
| #include "hex.h" | |
| #include "khash.h" | |
| #include "object.h" | |
| #include "odb.h" | |
| #include "oid-array.h" | |
| #include "oidset.h" | |
| #include "tree.h" | |
| #include "tree-walk.h" | |
| #include "tree-verify.h" | |
| #include "packfile.h" | |
| #include "trace2.h" | |
| enum tree_state { | |
| TREE_UNTRUSTED = 0, | |
| TREE_TRUSTED = 1, | |
| TREE_EXPANDED = 2, | |
| }; | |
| /* Values are enum tree_state, stored as a byte to keep the map small. */ | |
| KHASH_INIT(oid_tree, struct object_id, unsigned char, 1, | |
| oidhash_by_value, oideq_by_value) | |
| static enum tree_state tree_map_get(kh_oid_tree_t *m, | |
| const struct object_id *oid) | |
| { | |
| khint_t pos = kh_get_oid_tree(m, *oid); | |
| if (pos == kh_end(m)) | |
| return TREE_UNTRUSTED; | |
| return kh_val(m, pos); | |
| } | |
| static void tree_map_add(kh_oid_tree_t *m, const struct object_id *oid, | |
| enum tree_state state) | |
| { | |
| int added; | |
| khint_t pos = kh_put_oid_tree(m, *oid, &added); | |
| if (added) | |
| kh_val(m, pos) = state; | |
| else if (state > kh_val(m, pos)) | |
| kh_val(m, pos) = state; | |
| } | |
| struct work_item { | |
| struct name_entry entry; | |
| struct oid_array parent_trees; | |
| }; | |
| /* The entries of a tree that still need to be verified. */ | |
| struct work_list { | |
| struct work_item *items; | |
| size_t nr, alloc; | |
| bool has_subtrees; | |
| }; | |
| static void work_list_clear(struct work_list *list) | |
| { | |
| for (size_t i = 0; i < list->nr; i++) | |
| oid_array_clear(&list->items[i].parent_trees); | |
| free(list->items); | |
| } | |
| struct verify_state { | |
| kh_oid_tree_t *trees; | |
| struct oidset trusted_blobs; | |
| size_t trees_loaded; | |
| size_t blobs_checked; | |
| int exclude_promisor_objects; | |
| /* | |
| * Scratch space for reading one tree at a time. It lives here | |
| * rather than on the stack to keep the frame of the recursive | |
| * verify_tree() small. | |
| */ | |
| struct tree_desc desc; | |
| struct name_entry entry; | |
| }; | |
| /* | |
| * Merge-walk the work list against one base tree entry, recording | |
| * same-path parent subtrees as recursive comparison bases. | |
| * Returns the updated work-list cursor. | |
| */ | |
| static size_t collect_subtree_bases(struct work_list *list, size_t wi, | |
| const struct name_entry *entry) | |
| { | |
| while (wi < list->nr) { | |
| struct work_item *item = &list->items[wi]; | |
| int cmp = base_name_compare(item->entry.path, | |
| item->entry.pathlen, | |
| item->entry.mode, | |
| entry->path, entry->pathlen, | |
| entry->mode); | |
| if (cmp > 0) | |
| break; | |
| if (cmp < 0) { | |
| wi++; | |
| continue; | |
| } | |
| if (S_ISDIR(item->entry.mode) && S_ISDIR(entry->mode)) | |
| oid_array_append(&item->parent_trees, &entry->oid); | |
| return wi + 1; | |
| } | |
| return wi; | |
| } | |
| /* | |
| * Report an object that could not be read, telling a missing object | |
| * apart from one that exists but cannot be read, e.g. because it is | |
| * corrupt. | |
| */ | |
| static int unreadable_object_error(struct repository *repo, | |
| const struct object_id *oid, | |
| const char *type) | |
| { | |
| if (!odb_has_object(repo->objects, oid, 0)) | |
| return error(_("missing %s object %s"), type, oid_to_hex(oid)); | |
| return error(_("unable to read %s object %s"), type, oid_to_hex(oid)); | |
| } | |
| static int verify_blob(struct repository *repo, | |
| const struct object_id *oid, | |
| struct verify_state *vs) | |
| { | |
| struct object_info oi = OBJECT_INFO_INIT; | |
| enum object_type type; | |
| if (oidset_contains(&vs->trusted_blobs, oid)) | |
| return 0; | |
| vs->blobs_checked++; | |
| oi.typep = &type; | |
| if (odb_read_object_info_extended(repo->objects, oid, &oi, | |
| OBJECT_INFO_LOOKUP_REPLACE | | |
| OBJECT_INFO_SKIP_FETCH_OBJECT)) { | |
| if (vs->exclude_promisor_objects && | |
| is_promisor_object(repo, oid)) | |
| return 0; | |
| return unreadable_object_error(repo, oid, "blob"); | |
| } | |
| if (type != OBJ_BLOB) | |
| return error(_("object %s is a %s, not a %s"), | |
| oid_to_hex(oid), type_name(type), "blob"); | |
| oidset_insert(&vs->trusted_blobs, oid); | |
| return 0; | |
| } | |
| /* | |
| * Read a tree object into *bufp. Returns -1 if the object exists | |
| * but is not a tree. Otherwise returns 0, leaving *bufp NULL if the | |
| * object could not be read. | |
| */ | |
| static int read_tree_object(struct object_database *odb, | |
| const struct object_id *oid, | |
| void **bufp, size_t *sizep) | |
| { | |
| struct object_info oi = OBJECT_INFO_INIT; | |
| enum object_type type; | |
| oi.typep = &type; | |
| oi.sizep = sizep; | |
| oi.contentp = bufp; | |
| if (odb_read_object_info_extended(odb, oid, &oi, | |
| OBJECT_INFO_LOOKUP_REPLACE | | |
| OBJECT_INFO_SKIP_FETCH_OBJECT)) | |
| *bufp = NULL; | |
| if (*bufp && type != OBJ_TREE) { | |
| FREE_AND_NULL(*bufp); | |
| return error(_("object %s is a %s, not a %s"), | |
| oid_to_hex(oid), type_name(type), "tree"); | |
| } | |
| return 0; | |
| } | |
| /* List the entries of a tree that are not yet known to be trusted. */ | |
| static void list_untrusted_entries(struct verify_state *vs, | |
| const struct object_id *tree_oid, | |
| void *tree_buf, size_t tree_size, | |
| struct work_list *list) | |
| { | |
| struct name_entry *entry = &vs->entry; | |
| init_tree_desc(&vs->desc, tree_oid, tree_buf, tree_size); | |
| while (tree_entry(&vs->desc, entry)) { | |
| if (S_ISGITLINK(entry->mode)) | |
| continue; | |
| if (S_ISDIR(entry->mode)) { | |
| if (tree_map_get(vs->trees, &entry->oid) >= | |
| TREE_TRUSTED) | |
| continue; | |
| list->has_subtrees = true; | |
| } else { | |
| if (oidset_contains(&vs->trusted_blobs, &entry->oid)) | |
| continue; | |
| } | |
| ALLOC_GROW(list->items, list->nr + 1, list->alloc); | |
| memset(&list->items[list->nr], 0, sizeof(list->items[0])); | |
| list->items[list->nr++].entry = *entry; | |
| } | |
| } | |
| /* | |
| * Scan one trusted base tree: expand it if that has not happened yet, | |
| * and collect its same-path subtrees as bases for untrusted subtrees. | |
| */ | |
| static int scan_base_tree(struct repository *repo, struct verify_state *vs, | |
| const struct object_id *base_oid, | |
| struct work_list *list) | |
| { | |
| int expanded = tree_map_get(vs->trees, base_oid) >= TREE_EXPANDED; | |
| struct name_entry *entry = &vs->entry; | |
| size_t wi = 0, base_size; | |
| void *base_buf; | |
| if (expanded && !list->has_subtrees) | |
| return 0; | |
| if (read_tree_object(repo->objects, base_oid, &base_buf, &base_size)) | |
| return -1; | |
| if (!base_buf) { | |
| if (vs->exclude_promisor_objects && | |
| is_promisor_object(repo, base_oid)) | |
| return 0; | |
| return unreadable_object_error(repo, base_oid, "tree"); | |
| } | |
| vs->trees_loaded++; | |
| init_tree_desc(&vs->desc, base_oid, base_buf, base_size); | |
| while (tree_entry(&vs->desc, entry)) { | |
| if (S_ISGITLINK(entry->mode)) | |
| continue; | |
| /* Same-path subtree as a base for an untrusted one. */ | |
| if (list->has_subtrees) | |
| wi = collect_subtree_bases(list, wi, entry); | |
| /* Expand: the base is trusted, so are its entries. */ | |
| if (!expanded) { | |
| if (S_ISDIR(entry->mode)) | |
| tree_map_add(vs->trees, &entry->oid, | |
| TREE_TRUSTED); | |
| else | |
| oidset_insert(&vs->trusted_blobs, | |
| &entry->oid); | |
| } | |
| } | |
| if (!expanded) | |
| tree_map_add(vs->trees, base_oid, TREE_EXPANDED); | |
| free(base_buf); | |
| return 0; | |
| } | |
| /* | |
| * Verify a tree against the trusted base trees, loading as little as | |
| * possible: | |
| * | |
| * First list the entries that are not yet known to be trusted. When | |
| * the parent trees have been expanded before, only the changed entries | |
| * remain. | |
| * | |
| * Then scan the trusted base trees (the same-path trees of the parent | |
| * commits). A base that has not been expanded yet is read so that its | |
| * entries can be marked trusted; this often shows that listed entries | |
| * are trusted after all. An already expanded base is still read when | |
| * there are untrusted subtrees, because its subtree at the same path is | |
| * what lets the untrusted subtree skip its own unchanged entries in | |
| * turn. | |
| * | |
| * Finally check that the remaining blobs exist, and recurse into the | |
| * remaining subtrees with the same-path subtrees as their bases. | |
| */ | |
| static int verify_tree(struct repository *repo, | |
| const struct object_id *tree_oid, | |
| const struct oid_array *trusted_bases, | |
| struct verify_state *vs, int depth) | |
| { | |
| struct work_list list = { 0 }; | |
| size_t i, tree_size; | |
| void *tree_buf; | |
| int ret = 0; | |
| /* | |
| * Enforce core.maxTreeDepth like process_tree() in list-objects.c, | |
| * so that the full and incremental checks reject the same trees. | |
| */ | |
| if (depth > repo->settings.max_allowed_tree_depth) | |
| return error(_("exceeded maximum allowed tree depth")); | |
| if (tree_map_get(vs->trees, tree_oid) >= TREE_TRUSTED) | |
| return 0; | |
| if (read_tree_object(repo->objects, tree_oid, &tree_buf, &tree_size)) | |
| return -1; | |
| if (!tree_buf) { | |
| /* | |
| * A missing tree that is promised can be fetched on demand, | |
| * so it is not an error, as in the full check. | |
| */ | |
| if (vs->exclude_promisor_objects && | |
| is_promisor_object(repo, tree_oid)) | |
| return 0; | |
| return unreadable_object_error(repo, tree_oid, "tree"); | |
| } | |
| vs->trees_loaded++; | |
| list_untrusted_entries(vs, tree_oid, tree_buf, tree_size, &list); | |
| /* Scan the base trees, unless nothing is left to verify. */ | |
| for (i = 0; list.nr && i < trusted_bases->nr; i++) { | |
| ret = scan_base_tree(repo, vs, &trusted_bases->oid[i], &list); | |
| if (ret) | |
| goto done; | |
| } | |
| /* | |
| * Check the remaining entries in tree order. Verifying a subtree | |
| * can show that later entries are trusted, which then makes them | |
| * cheap to skip. | |
| */ | |
| for (i = 0; i < list.nr && !ret; i++) { | |
| struct work_item *item = &list.items[i]; | |
| if (S_ISDIR(item->entry.mode)) | |
| ret = verify_tree(repo, &item->entry.oid, | |
| &item->parent_trees, vs, depth + 1); | |
| else | |
| ret = verify_blob(repo, &item->entry.oid, vs); | |
| } | |
| if (!ret) | |
| tree_map_add(vs->trees, tree_oid, TREE_EXPANDED); | |
| done: | |
| free(tree_buf); | |
| work_list_clear(&list); | |
| return ret; | |
| } | |
| static int verify_commit_tree(struct repository *repo, | |
| struct commit *commit, | |
| struct verify_state *vs) | |
| { | |
| struct oid_array parent_trees = OID_ARRAY_INIT; | |
| const struct object_id *tree_oid; | |
| struct commit_list *p; | |
| int ret; | |
| /* | |
| * Parent trees are trusted: boundary parents are already | |
| * connected, and earlier incoming parents were verified | |
| * first due to the topological processing order. | |
| * | |
| * The revision walk skips missing parents that are promised | |
| * (see process_parents()), so do the same here. Such a parent | |
| * contributes no base, which only means less of the new tree | |
| * can be skipped. | |
| */ | |
| for (p = commit->parents; p; p = p->next) { | |
| if (repo_parse_commit_gently(repo, p->item, 1) < 0) { | |
| if (vs->exclude_promisor_objects && | |
| is_promisor_object(repo, &p->item->object.oid)) | |
| continue; | |
| ret = error(_("unable to parse commit %s"), | |
| oid_to_hex(&p->item->object.oid)); | |
| goto out; | |
| } | |
| tree_oid = get_commit_tree_oid(p->item); | |
| if (!tree_oid) { | |
| ret = error(_("unable to load root tree for commit %s"), | |
| oid_to_hex(&p->item->object.oid)); | |
| goto out; | |
| } | |
| tree_map_add(vs->trees, tree_oid, TREE_TRUSTED); | |
| oid_array_append(&parent_trees, tree_oid); | |
| } | |
| tree_oid = get_commit_tree_oid(commit); | |
| if (!tree_oid) { | |
| ret = error(_("unable to load root tree for commit %s"), | |
| oid_to_hex(&commit->object.oid)); | |
| goto out; | |
| } | |
| ret = verify_tree(repo, tree_oid, &parent_trees, vs, 0); | |
| out: | |
| oid_array_clear(&parent_trees); | |
| return ret; | |
| } | |
| int verify_commits_incremental(struct repository *repo, | |
| struct commit_list **commits, | |
| int exclude_promisor_objects) | |
| { | |
| struct verify_state vs = { 0 }; | |
| struct commit_list *iter; | |
| unsigned nr_before; | |
| int ret = 0; | |
| vs.trees = kh_init_oid_tree(); | |
| vs.exclude_promisor_objects = exclude_promisor_objects; | |
| /* | |
| * Ancestors must be verified before descendants so that parent | |
| * trees can be trusted without re-verification. Sort explicitly | |
| * rather than relying on the caller's ordering. | |
| * | |
| * sort_in_topological_order() silently drops cycle members, | |
| * so explicitly check if the size has changed. | |
| */ | |
| nr_before = commit_list_count(*commits); | |
| sort_in_topological_order(commits, REV_SORT_IN_GRAPH_ORDER); | |
| if (commit_list_count(*commits) < nr_before) { | |
| ret = error(_("cycle detected in incoming commit graph")); | |
| goto out; | |
| } | |
| *commits = commit_list_reverse(*commits); | |
| for (iter = *commits; iter && !ret; iter = iter->next) | |
| ret = verify_commit_tree(repo, iter->item, &vs); | |
| out: | |
| kh_destroy_oid_tree(vs.trees); | |
| oidset_clear(&vs.trusted_blobs); | |
| trace2_data_intmax("connectivity", repo, | |
| "trees_loaded", (intmax_t)vs.trees_loaded); | |
| trace2_data_intmax("connectivity", repo, | |
| "blobs_checked", (intmax_t)vs.blobs_checked); | |
| return ret; | |
| } |