fsck: handle bad trees like other errors

Instead of dying when fsck hits a malformed tree object, log the error like any other and continue. Now fsck can tell the user which tree is bad, too. Signed-off-by: David Turner <dturner@twosigma.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

David Turner committed Sep 27, 2016 at 16:59 UTC 8354fa3d4ca50850760ceee9054e3e7a799a4d62
4 files changed +106 -19
fsck.c
+12 -6
@@ -347,8 +347,9 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
347 return -1;
348
349 name = get_object_name(options, &tree->object);
350 - init_tree_desc(&desc, tree->buffer, tree->size);
351 - while (tree_entry(&desc, &entry)) {
350 + if (init_tree_desc_gently(&desc, tree->buffer, tree->size))
351 + return -1;
352 + while (tree_entry_gently(&desc, &entry)) {
353 struct object *obj;
354 int result;
355
@@ -520,7 +521,7 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con
521
522 static int fsck_tree(struct tree *item, struct fsck_options *options)
523 {
523 - int retval;
524 + int retval = 0;
525 int has_null_sha1 = 0;
526 int has_full_path = 0;
527 int has_empty_name = 0;
@@ -535,7 +536,10 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
536 unsigned o_mode;
537 const char *o_name;
538
538 - init_tree_desc(&desc, item->buffer, item->size);
539 + if (init_tree_desc_gently(&desc, item->buffer, item->size)) {
540 + retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
541 + return retval;
542 + }
543
544 o_mode = 0;
545 o_name = NULL;
@@ -556,7 +560,10 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
560 is_hfs_dotgit(name) ||
561 is_ntfs_dotgit(name));
562 has_zero_pad |= *(char *)desc.buffer == '0';
559 - update_tree_entry(&desc);
563 + if (update_tree_entry_gently(&desc)) {
564 + retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
565 + break;
566 + }
567
568 switch (mode) {
569 /*
@@ -597,7 +604,6 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
604 o_name = name;
605 }
606
600 - retval = 0;
607 if (has_null_sha1)
608 retval += report(options, &item->object, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
609 if (has_full_path)
t/t1450-fsck.sh
+14 -2
@@ -188,8 +188,7 @@ test_expect_success 'commit with NUL in header' '
188 grep "error in commit $new.*unterminated header: NUL at offset" out
189 '
190
191 -test_expect_success 'malformatted tree object' '
192 - test_when_finished "git update-ref -d refs/tags/wrong" &&
191 +test_expect_success 'tree object with duplicate entries' '
192 test_when_finished "remove_object \$T" &&
193 T=$(
194 GIT_INDEX_FILE=test-index &&
@@ -208,6 +207,19 @@ test_expect_success 'malformatted tree object' '
207 grep "error in tree .*contains duplicate file entries" out
208 '
209
210 +test_expect_success 'unparseable tree object' '
211 + test_when_finished "git update-ref -d refs/heads/wrong" &&
212 + test_when_finished "remove_object \$tree_sha1" &&
213 + test_when_finished "remove_object \$commit_sha1" &&
214 + tree_sha1=$(printf "100644 \0twenty-bytes-of-junk" | git hash-object -t tree --stdin -w --literally) &&
215 + commit_sha1=$(git commit-tree $tree_sha1) &&
216 + git update-ref refs/heads/wrong $commit_sha1 &&
217 + test_must_fail git fsck 2>out &&
218 + test_i18ngrep "error: empty filename in tree entry" out &&
219 + test_i18ngrep "$tree_sha1" out &&
220 + test_i18ngrep ! "fatal: empty filename in tree entry" out
221 +'
222 +
223 test_expect_success 'tag pointing to nonexistent' '
224 cat >invalid-tag <<-\EOF &&
225 object ffffffffffffffffffffffffffffffffffffffff
tree-walk.c
+72 -11
@@ -22,33 +22,60 @@ static const char *get_mode(const char *str, unsigned int *modep)
22 return str;
23 }
24
25 -static void decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size)
25 +static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err)
26 {
27 const char *path;
28 unsigned int mode, len;
29
30 - if (size < 23 || buf[size - 21])
31 - die(_("too-short tree object"));
30 + if (size < 23 || buf[size - 21]) {
31 + strbuf_addstr(err, _("too-short tree object"));
32 + return -1;
33 + }
34
35 path = get_mode(buf, &mode);
34 - if (!path)
35 - die(_("malformed mode in tree entry for tree"));
36 - if (!*path)
37 - die(_("empty filename in tree entry for tree"));
36 + if (!path) {
37 + strbuf_addstr(err, _("malformed mode in tree entry"));
38 + return -1;
39 + }
40 + if (!*path) {
41 + strbuf_addstr(err, _("empty filename in tree entry"));
42 + return -1;
43 + }
44 len = strlen(path) + 1;
45
46 /* Initialize the descriptor entry */
47 desc->entry.path = path;
48 desc->entry.mode = canon_mode(mode);
49 desc->entry.oid = (const struct object_id *)(path + len);
50 +
51 + return 0;
52 }
53
46 -void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size)
54 +static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, unsigned long size, struct strbuf *err)
55 {
56 desc->buffer = buffer;
57 desc->size = size;
58 if (size)
51 - decode_tree_entry(desc, buffer, size);
59 + return decode_tree_entry(desc, buffer, size, err);
60 + return 0;
61 +}
62 +
63 +void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size)
64 +{
65 + struct strbuf err = STRBUF_INIT;
66 + if (init_tree_desc_internal(desc, buffer, size, &err))
67 + die("%s", err.buf);
68 + strbuf_release(&err);
69 +}
70 +
71 +int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size)
72 +{
73 + struct strbuf err = STRBUF_INIT;
74 + int result = init_tree_desc_internal(desc, buffer, size, &err);
75 + if (result)
76 + error("%s", err.buf);
77 + strbuf_release(&err);
78 + return result;
79 }
80
81 void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
@@ -75,7 +102,7 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a)
102 *a = t->entry;
103 }
104
78 -void update_tree_entry(struct tree_desc *desc)
105 +static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err)
106 {
107 const void *buf = desc->buffer;
108 const unsigned char *end = desc->entry.oid->hash + 20;
@@ -89,7 +116,30 @@ void update_tree_entry(struct tree_desc *desc)
116 desc->buffer = buf;
117 desc->size = size;
118 if (size)
92 - decode_tree_entry(desc, buf, size);
119 + return decode_tree_entry(desc, buf, size, err);
120 + return 0;
121 +}
122 +
123 +void update_tree_entry(struct tree_desc *desc)
124 +{
125 + struct strbuf err = STRBUF_INIT;
126 + if (update_tree_entry_internal(desc, &err))
127 + die("%s", err.buf);
128 + strbuf_release(&err);
129 +}
130 +
131 +int update_tree_entry_gently(struct tree_desc *desc)
132 +{
133 + struct strbuf err = STRBUF_INIT;
134 + if (update_tree_entry_internal(desc, &err)) {
135 + error("%s", err.buf);
136 + strbuf_release(&err);
137 + /* Stop processing this tree after error */
138 + desc->size = 0;
139 + return -1;
140 + }
141 + strbuf_release(&err);
142 + return 0;
143 }
144
145 int tree_entry(struct tree_desc *desc, struct name_entry *entry)
@@ -102,6 +152,17 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry)
152 return 1;
153 }
154
155 +int tree_entry_gently(struct tree_desc *desc, struct name_entry *entry)
156 +{
157 + if (!desc->size)
158 + return 0;
159 +
160 + *entry = desc->entry;
161 + if (update_tree_entry_gently(desc))
162 + return 0;
163 + return 1;
164 +}
165 +
166 void setup_traverse_info(struct traverse_info *info, const char *base)
167 {
168 int pathlen = strlen(base);
tree-walk.h
+8
@@ -25,14 +25,22 @@ static inline int tree_entry_len(const struct name_entry *ne)
25 return (const char *)ne->oid - ne->path - 1;
26 }
27
28 +/*
29 + * The _gently versions of these functions warn and return false on a
30 + * corrupt tree entry rather than dying,
31 + */
32 +
33 void update_tree_entry(struct tree_desc *);
34 +int update_tree_entry_gently(struct tree_desc *);
35 void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size);
36 +int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size);
37
38 /*
39 * Helper function that does both tree_entry_extract() and update_tree_entry()
40 * and returns true for success
41 */
42 int tree_entry(struct tree_desc *, struct name_entry *);
43 +int tree_entry_gently(struct tree_desc *, struct name_entry *);
44
45 void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1);
46