cache-tree: refactor verification to return error codes

The function `cache_tree_verify()` will `BUG()` whenever it finds that
the cache-tree extension of the index is corrupt. The function is thus
inherently untestable because the resulting call to `abort()` will be
detected by our testing framework and labelled an error. And rightfully
so: it shouldn't ever be possible to hit bugs, as they should indicate a
programming error rather than corruption of on-disk state.

Refactor the function to instead return error codes. This also ensures
that the function can be used e.g. by git-fsck(1) without the whole
process dying. Furthermore, this refactoring plugs some memory leaks
when returning early by creating a common exit path.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Patrick Steinhardt 2024-10-07 06:38:15 +02:00 committed by Junio C Hamano
parent 3969d78396
commit 9f119599a6
4 changed files with 79 additions and 35 deletions

View File

@ -2,6 +2,7 @@


#include "git-compat-util.h" #include "git-compat-util.h"
#include "environment.h" #include "environment.h"
#include "gettext.h"
#include "hex.h" #include "hex.h"
#include "lockfile.h" #include "lockfile.h"
#include "tree.h" #include "tree.h"
@ -864,15 +865,15 @@ int cache_tree_matches_traversal(struct cache_tree *root,
return 0; return 0;
} }


static void verify_one_sparse(struct index_state *istate, static int verify_one_sparse(struct index_state *istate,
struct strbuf *path, struct strbuf *path,
int pos) int pos)
{ {
struct cache_entry *ce = istate->cache[pos]; struct cache_entry *ce = istate->cache[pos];

if (!S_ISSPARSEDIR(ce->ce_mode)) if (!S_ISSPARSEDIR(ce->ce_mode))
BUG("directory '%s' is present in index, but not sparse", return error(_("directory '%s' is present in index, but not sparse"),
path->buf); path->buf);
return 0;
} }


/* /*
@ -881,6 +882,7 @@ static void verify_one_sparse(struct index_state *istate,
* 1 - Restart verification - a call to ensure_full_index() freed the cache * 1 - Restart verification - a call to ensure_full_index() freed the cache
* tree that is being verified and verification needs to be restarted from * tree that is being verified and verification needs to be restarted from
* the new toplevel cache tree. * the new toplevel cache tree.
* -1 - Verification failed.
*/ */
static int verify_one(struct repository *r, static int verify_one(struct repository *r,
struct index_state *istate, struct index_state *istate,
@ -890,18 +892,23 @@ static int verify_one(struct repository *r,
int i, pos, len = path->len; int i, pos, len = path->len;
struct strbuf tree_buf = STRBUF_INIT; struct strbuf tree_buf = STRBUF_INIT;
struct object_id new_oid; struct object_id new_oid;
int ret;


for (i = 0; i < it->subtree_nr; i++) { for (i = 0; i < it->subtree_nr; i++) {
strbuf_addf(path, "%s/", it->down[i]->name); strbuf_addf(path, "%s/", it->down[i]->name);
if (verify_one(r, istate, it->down[i]->cache_tree, path)) ret = verify_one(r, istate, it->down[i]->cache_tree, path);
return 1; if (ret)
goto out;

strbuf_setlen(path, len); strbuf_setlen(path, len);
} }


if (it->entry_count < 0 || if (it->entry_count < 0 ||
/* no verification on tests (t7003) that replace trees */ /* no verification on tests (t7003) that replace trees */
lookup_replace_object(r, &it->oid) != &it->oid) lookup_replace_object(r, &it->oid) != &it->oid) {
return 0; ret = 0;
goto out;
}


if (path->len) { if (path->len) {
/* /*
@ -911,12 +918,14 @@ static int verify_one(struct repository *r,
*/ */
int is_sparse = istate->sparse_index; int is_sparse = istate->sparse_index;
pos = index_name_pos(istate, path->buf, path->len); pos = index_name_pos(istate, path->buf, path->len);
if (is_sparse && !istate->sparse_index) if (is_sparse && !istate->sparse_index) {
return 1; ret = 1;
goto out;
}


if (pos >= 0) { if (pos >= 0) {
verify_one_sparse(istate, path, pos); ret = verify_one_sparse(istate, path, pos);
return 0; goto out;
} }


pos = -pos - 1; pos = -pos - 1;
@ -934,16 +943,23 @@ static int verify_one(struct repository *r,
unsigned mode; unsigned mode;
int entlen; int entlen;


if (ce->ce_flags & (CE_STAGEMASK | CE_INTENT_TO_ADD | CE_REMOVE)) if (ce->ce_flags & (CE_STAGEMASK | CE_INTENT_TO_ADD | CE_REMOVE)) {
BUG("%s with flags 0x%x should not be in cache-tree", ret = error(_("%s with flags 0x%x should not be in cache-tree"),
ce->name, ce->ce_flags); ce->name, ce->ce_flags);
goto out;
}

name = ce->name + path->len; name = ce->name + path->len;
slash = strchr(name, '/'); slash = strchr(name, '/');
if (slash) { if (slash) {
entlen = slash - name; entlen = slash - name;

sub = find_subtree(it, ce->name + path->len, entlen, 0); sub = find_subtree(it, ce->name + path->len, entlen, 0);
if (!sub || sub->cache_tree->entry_count < 0) if (!sub || sub->cache_tree->entry_count < 0) {
BUG("bad subtree '%.*s'", entlen, name); ret = error(_("bad subtree '%.*s'"), entlen, name);
goto out;
}

oid = &sub->cache_tree->oid; oid = &sub->cache_tree->oid;
mode = S_IFDIR; mode = S_IFDIR;
i += sub->cache_tree->entry_count; i += sub->cache_tree->entry_count;
@ -956,27 +972,50 @@ static int verify_one(struct repository *r,
strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0'); strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0');
strbuf_add(&tree_buf, oid->hash, r->hash_algo->rawsz); strbuf_add(&tree_buf, oid->hash, r->hash_algo->rawsz);
} }

hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, OBJ_TREE, hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, OBJ_TREE,
&new_oid); &new_oid);
if (!oideq(&new_oid, &it->oid))
BUG("cache-tree for path %.*s does not match. " if (!oideq(&new_oid, &it->oid)) {
"Expected %s got %s", len, path->buf, ret = error(_("cache-tree for path %.*s does not match. "
oid_to_hex(&new_oid), oid_to_hex(&it->oid)); "Expected %s got %s"), len, path->buf,
oid_to_hex(&new_oid), oid_to_hex(&it->oid));
goto out;
}

ret = 0;
out:
strbuf_setlen(path, len); strbuf_setlen(path, len);
strbuf_release(&tree_buf); strbuf_release(&tree_buf);
return 0; return ret;
} }


void cache_tree_verify(struct repository *r, struct index_state *istate) int cache_tree_verify(struct repository *r, struct index_state *istate)
{ {
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
int ret;


if (!istate->cache_tree) if (!istate->cache_tree) {
return; ret = 0;
if (verify_one(r, istate, istate->cache_tree, &path)) { goto out;
}

ret = verify_one(r, istate, istate->cache_tree, &path);
if (ret < 0)
goto out;
if (ret > 0) {
strbuf_reset(&path); strbuf_reset(&path);
if (verify_one(r, istate, istate->cache_tree, &path))
ret = verify_one(r, istate, istate->cache_tree, &path);
if (ret < 0)
goto out;
if (ret > 0)
BUG("ensure_full_index() called twice while verifying cache tree"); BUG("ensure_full_index() called twice while verifying cache tree");
} }

ret = 0;

out:
strbuf_release(&path); strbuf_release(&path);
return ret;
} }

View File

@ -33,7 +33,7 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);


int cache_tree_fully_valid(struct cache_tree *); int cache_tree_fully_valid(struct cache_tree *);
int cache_tree_update(struct index_state *, int); int cache_tree_update(struct index_state *, int);
void cache_tree_verify(struct repository *, struct index_state *); int cache_tree_verify(struct repository *, struct index_state *);


/* bitmasks to write_index_as_tree flags */ /* bitmasks to write_index_as_tree flags */
#define WRITE_TREE_MISSING_OK 1 #define WRITE_TREE_MISSING_OK 1

View File

@ -3331,8 +3331,9 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
int new_shared_index, ret, test_split_index_env; int new_shared_index, ret, test_split_index_env;
struct split_index *si = istate->split_index; struct split_index *si = istate->split_index;


if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0)) if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0) &&
cache_tree_verify(the_repository, istate); cache_tree_verify(the_repository, istate) < 0)
return -1;


if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) { if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
if (flags & COMMIT_LOCK) if (flags & COMMIT_LOCK)

View File

@ -2070,9 +2070,13 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (o->dst_index) { if (o->dst_index) {
move_index_extensions(&o->internal.result, o->src_index); move_index_extensions(&o->internal.result, o->src_index);
if (!ret) { if (!ret) {
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0)) if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0) &&
cache_tree_verify(the_repository, cache_tree_verify(the_repository,
&o->internal.result); &o->internal.result) < 0) {
ret = -1;
goto done;
}

if (!o->skip_cache_tree_update && if (!o->skip_cache_tree_update &&
!cache_tree_fully_valid(o->internal.result.cache_tree)) !cache_tree_fully_valid(o->internal.result.cache_tree))
cache_tree_update(&o->internal.result, cache_tree_update(&o->internal.result,