fsck: don't hard die on invalid object types

Change the error fsck emits on invalid object types, such as: $ git hash-object --stdin -w -t garbage --literally </dev/null <OID> From the very ungraceful error of: $ git fsck fatal: invalid object type $ To: $ git fsck error: <OID>: object is of unknown type 'garbage': <OID_PATH> [ other fsck output ] We'll still exit with non-zero, but now we'll finish the rest of the traversal. The tests that's being added here asserts that we'll still complain about other fsck issues (e.g. an unrelated dangling blob). To do this we need to pass down the "OBJECT_INFO_ALLOW_UNKNOWN_TYPE" flag from read_loose_object() through to parse_loose_header(). Since the read_loose_object() function is only used in builtin/fsck.c we can simply change it to accept a "struct object_info" (which contains the OBJECT_INFO_ALLOW_UNKNOWN_TYPE in its flags). See f6371f92104 (sha1_file: add read_loose_object() function, 2017-01-13) for the introduction of read_loose_object(). Since we'll need a "struct strbuf" to hold the "type_name" let's pass it to the for_each_loose_file_in_objdir() callback to avoid allocating a new one for each loose object in the iteration. It also makes the memory management simpler than sticking it in fsck_loose() itself, as we'll only need to strbuf_reset() it, with no need to do a strbuf_release() before each "return". Before this commit we'd never check the "type" if read_loose_object() failed, but now we do. We therefore need to initialize it to OBJ_NONE to be able to tell the difference between e.g. its unpack_loose_header() having failed, and us getting past that and into parse_loose_header(). Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Oct 1, 2021 at 11:16 UTC 31deb28f5e0c85e8bd556ba135e5f0e0926bad7a
4 files changed +49 -30
builtin/fsck.c
+31 -6
@@ -592,18 +592,36 @@ static void get_default_heads(void)
592 }
593 }
594
595 +struct for_each_loose_cb
596 +{
597 + struct progress *progress;
598 + struct strbuf obj_type;
599 +};
600 +
601 static int fsck_loose(const struct object_id *oid, const char *path, void *data)
602 {
603 + struct for_each_loose_cb *cb_data = data;
604 struct object *obj;
598 - enum object_type type;
605 + enum object_type type = OBJ_NONE;
606 unsigned long size;
607 void *contents;
608 int eaten;
609 + struct object_info oi = OBJECT_INFO_INIT;
610 + int err = 0;
611
603 - if (read_loose_object(path, oid, &type, &size, &contents) < 0) {
612 + strbuf_reset(&cb_data->obj_type);
613 + oi.type_name = &cb_data->obj_type;
614 + oi.sizep = &size;
615 + oi.typep = &type;
616 +
617 + if (read_loose_object(path, oid, &contents, &oi) < 0)
618 + err = error(_("%s: object corrupt or missing: %s"),
619 + oid_to_hex(oid), path);
620 + if (type != OBJ_NONE && type < 0)
621 + err = error(_("%s: object is of unknown type '%s': %s"),
622 + oid_to_hex(oid), cb_data->obj_type.buf, path);
623 + if (err < 0) {
624 errors_found |= ERROR_OBJECT;
605 - error(_("%s: object corrupt or missing: %s"),
606 - oid_to_hex(oid), path);
625 return 0; /* keep checking other objects */
626 }
627
@@ -639,8 +657,10 @@ static int fsck_cruft(const char *basename, const char *path, void *data)
657 return 0;
658 }
659
642 -static int fsck_subdir(unsigned int nr, const char *path, void *progress)
660 +static int fsck_subdir(unsigned int nr, const char *path, void *data)
661 {
662 + struct for_each_loose_cb *cb_data = data;
663 + struct progress *progress = cb_data->progress;
664 display_progress(progress, nr + 1);
665 return 0;
666 }
@@ -648,6 +668,10 @@ static int fsck_subdir(unsigned int nr, const char *path, void *progress)
668 static void fsck_object_dir(const char *path)
669 {
670 struct progress *progress = NULL;
671 + struct for_each_loose_cb cb_data = {
672 + .obj_type = STRBUF_INIT,
673 + .progress = progress,
674 + };
675
676 if (verbose)
677 fprintf_ln(stderr, _("Checking object directory"));
@@ -656,9 +680,10 @@ static void fsck_object_dir(const char *path)
680 progress = start_progress(_("Checking object directories"), 256);
681
682 for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
659 - progress);
683 + &cb_data);
684 display_progress(progress, 256);
685 stop_progress(&progress);
686 + strbuf_release(&cb_data.obj_type);
687 }
688
689 static int fsck_head_link(const char *head_ref_name,
object-file.c
+6 -12
@@ -2520,18 +2520,15 @@ static int check_stream_oid(git_zstream *stream,
2520
2521 int read_loose_object(const char *path,
2522 const struct object_id *expected_oid,
2523 - enum object_type *type,
2524 - unsigned long *size,
2525 - void **contents)
2523 + void **contents,
2524 + struct object_info *oi)
2525 {
2526 int ret = -1;
2527 void *map = NULL;
2528 unsigned long mapsize;
2529 git_zstream stream;
2530 char hdr[MAX_HEADER_LEN];
2532 - struct object_info oi = OBJECT_INFO_INIT;
2533 - oi.typep = type;
2534 - oi.sizep = size;
2531 + unsigned long *size = oi->sizep;
2532
2533 *contents = NULL;
2534
@@ -2547,15 +2544,13 @@ int read_loose_object(const char *path,
2544 goto out;
2545 }
2546
2550 - if (parse_loose_header(hdr, &oi) < 0) {
2547 + if (parse_loose_header(hdr, oi) < 0) {
2548 error(_("unable to parse header of %s"), path);
2549 git_inflate_end(&stream);
2550 goto out;
2551 }
2555 - if (*type < 0)
2556 - die(_("invalid object type"));
2552
2558 - if (*type == OBJ_BLOB && *size > big_file_threshold) {
2553 + if (*oi->typep == OBJ_BLOB && *size > big_file_threshold) {
2554 if (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0)
2555 goto out;
2556 } else {
@@ -2566,8 +2561,7 @@ int read_loose_object(const char *path,
2561 goto out;
2562 }
2563 if (check_object_signature(the_repository, expected_oid,
2569 - *contents, *size,
2570 - type_name(*type))) {
2564 + *contents, *size, oi->type_name->buf)) {
2565 error(_("hash mismatch for %s (expected %s)"), path,
2566 oid_to_hex(expected_oid));
2567 free(*contents);
object-store.h
+3 -3
@@ -236,6 +236,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime);
236
237 /*
238 * Open the loose object at path, check its hash, and return the contents,
239 + * use the "oi" argument to assert things about the object, or e.g. populate its
240 * type, and size. If the object is a blob, then "contents" may return NULL,
241 * to allow streaming of large blobs.
242 *
@@ -243,9 +244,8 @@ int force_object_loose(const struct object_id *oid, time_t mtime);
244 */
245 int read_loose_object(const char *path,
246 const struct object_id *expected_oid,
246 - enum object_type *type,
247 - unsigned long *size,
248 - void **contents);
247 + void **contents,
248 + struct object_info *oi);
249
250 /* Retry packed storage after checking packed and loose storage */
251 #define HAS_OBJECT_RECHECK_PACKED 1
t/t1450-fsck.sh
+9 -9
@@ -85,11 +85,10 @@ test_expect_success 'object with hash and type mismatch' '
85 cmt=$(echo bogus | git commit-tree $tree) &&
86 git update-ref refs/heads/bogus $cmt &&
87
88 - cat >expect <<-\EOF &&
89 - fatal: invalid object type
90 - EOF
91 - test_must_fail git fsck 2>actual &&
92 - test_cmp expect actual
88 +
89 + test_must_fail git fsck 2>out &&
90 + grep "^error: hash mismatch for " out &&
91 + grep "^error: $oid: object is of unknown type '"'"'garbage'"'"'" out
92 )
93 '
94
@@ -910,19 +909,20 @@ test_expect_success 'detect corrupt index file in fsck' '
909 test_i18ngrep "bad index file" errors
910 '
911
913 -test_expect_success 'fsck hard errors on an invalid object type' '
912 +test_expect_success 'fsck error and recovery on invalid object type' '
913 git init --bare garbage-type &&
914 (
915 cd garbage-type &&
916
918 - git hash-object --stdin -w -t garbage --literally </dev/null &&
917 + garbage_blob=$(git hash-object --stdin -w -t garbage --literally </dev/null) &&
918
919 cat >err.expect <<-\EOF &&
920 fatal: invalid object type
921 EOF
922 test_must_fail git fsck >out 2>err &&
924 - test_cmp err.expect err &&
925 - test_must_be_empty out
923 + grep -e "^error" -e "^fatal" err >errors &&
924 + test_line_count = 1 errors &&
925 + grep "$garbage_blob: object is of unknown type '"'"'garbage'"'"':" err
926 )
927 '
928