Skip to content

Commit e859555

Browse files
shejialuogitster
authored andcommitted
packed-backend: add "packed-refs" header consistency check
In "packed-backend.c::create_snapshot", if there is a header (the line which starts with '#'), we will check whether the line starts with "# pack-refs with:". As we are going to implement the header consistency check, we should port this check into "packed_fsck". However, the above check is not enough, this is because "git pack-refs" will always write "PACKED_REFS_HEADER" which is a constant string to the "packed-refs" file. So, we should check the following things for the header. 1. If the header does not exist, we may report an error to the user because it should exist, but we do allow no header in "packed-refs" file. So, create a new fsck message "packedRefMissingHeader(INFO)" to warn the user and also keep compatibility. 2. If the header content does not start with "# packed-ref with:", we should report an error just like what "create_snapshot" does. So, create a new fsck message "badPackedRefHeader(ERROR)" for this. 3. If the header content is not the same as the constant string "PACKED_REFS_HEADER", ideally, we should report an error to the user. However, we allow other contents as long as the header content starts with "# packed-ref with:". To keep compatibility, create a new fsck message "unknownPackedRefHeader(INFO)" to warn about this. We may tighten this rule in the future. In order to achieve above checks, read the "packed-refs" file via "strbuf_read_file". Like what "create_snapshot" and other functions do, we could split the line by finding the next newline in the buf. If we cannot find a newline, this is an error. So, create a function "packed_fsck_ref_next_line" to find the next newline and if there is no such newline, use "packedRefEntryNotTerminated(INFO)" to report an error to the user. Then, parse the first line to apply the above three checks. Update the test to excise the code. However, when adding the new test for a bad header, the program will still die in the "create_snapshot" method. This is because we have checked the files-backend firstly and we use "parse_object" to check whether the object exists and whether the type is correct. This function will eventually call "create_snapshot" and "next_record" method, if there is something wrong with packed-backend, the program just dies. It's bad to just die the program because we want to report the problems as many as possible. We should avoid checking object and its type when packed-backend is broken. So, we should first check the consistency of the packed-backend then for files-backend. Add a new flag "safe_object_check" in "fsck_options", when there is anything wrong with the parsing process, set this flag to 0 to avoid checking objects in the later checks. Mentored-by: Patrick Steinhardt <[email protected]> Mentored-by: Karthik Nayak <[email protected]> Signed-off-by: shejialuo <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent fe9a6b7 commit e859555

File tree

5 files changed

+174
-3
lines changed

5 files changed

+174
-3
lines changed

Documentation/fsck-msgids.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
`badObjectSha1`::
1717
(ERROR) An object has a bad sha1.
1818

19+
`badPackedRefHeader`::
20+
(ERROR) The "packed-refs" file contains an invalid
21+
header.
22+
1923
`badParentSha1`::
2024
(ERROR) A commit object has a bad parent sha1.
2125

@@ -176,6 +180,13 @@
176180
`nullSha1`::
177181
(WARN) Tree contains entries pointing to a null sha1.
178182

183+
`packedRefEntryNotTerminated`::
184+
(ERROR) The "packed-refs" file contains an entry that is
185+
not terminated by a newline.
186+
187+
`packedRefMissingHeader`::
188+
(INFO) The "packed-refs" file does not contain the header.
189+
179190
`refMissingNewline`::
180191
(INFO) A loose ref that does not end with newline(LF). As
181192
valid implementations of Git never created such a loose ref
@@ -208,6 +219,11 @@
208219
`treeNotSorted`::
209220
(ERROR) A tree is not properly sorted.
210221

222+
`unknownPackedRefHeader`::
223+
(INFO) The "packed-refs" header starts with "# pack-refs with:"
224+
but the remaining content is not the same as what `git pack-refs`
225+
would write.
226+
211227
`unknownType`::
212228
(ERROR) Found an unknown object type.
213229

fsck.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ enum fsck_msg_type {
3030
FUNC(BAD_EMAIL, ERROR) \
3131
FUNC(BAD_NAME, ERROR) \
3232
FUNC(BAD_OBJECT_SHA1, ERROR) \
33+
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
3334
FUNC(BAD_PARENT_SHA1, ERROR) \
3435
FUNC(BAD_REF_CONTENT, ERROR) \
3536
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
5354
FUNC(MISSING_TYPE, ERROR) \
5455
FUNC(MISSING_TYPE_ENTRY, ERROR) \
5556
FUNC(MULTIPLE_AUTHORS, ERROR) \
57+
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
5658
FUNC(TREE_NOT_SORTED, ERROR) \
5759
FUNC(UNKNOWN_TYPE, ERROR) \
5860
FUNC(ZERO_PADDED_DATE, ERROR) \
@@ -90,6 +92,8 @@ enum fsck_msg_type {
9092
FUNC(REF_MISSING_NEWLINE, INFO) \
9193
FUNC(SYMREF_TARGET_IS_NOT_A_REF, INFO) \
9294
FUNC(TRAILING_REF_CONTENT, INFO) \
95+
FUNC(UNKNOWN_PACKED_REF_HEADER, INFO) \
96+
FUNC(PACKED_REF_MISSING_HEADER, INFO) \
9397
/* ignored (elevated when requested) */ \
9498
FUNC(EXTRA_HEADER_ENTRY, IGNORE)
9599

@@ -163,6 +167,7 @@ struct fsck_options {
163167
fsck_error error_func;
164168
unsigned strict;
165169
unsigned verbose;
170+
int safe_object_check;
166171
enum fsck_msg_type *msg_type;
167172
struct oidset skip_oids;
168173
struct oidset gitmodules_found;
@@ -198,6 +203,7 @@ struct fsck_options {
198203
}
199204
#define FSCK_REFS_OPTIONS_DEFAULT { \
200205
.error_func = fsck_refs_error_function, \
206+
.safe_object_check = 1, \
201207
}
202208

203209
/* descend in all linked child objects

refs/files-backend.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3648,7 +3648,7 @@ static int files_fsck_refs_oid(struct fsck_options *o,
36483648
struct object *obj;
36493649
int ret = 0;
36503650

3651-
if (is_promisor_object(ref_store->repo, oid))
3651+
if (!o->safe_object_check || is_promisor_object(ref_store->repo, oid))
36523652
return 0;
36533653

36543654
obj = parse_object(ref_store->repo, oid);
@@ -3868,8 +3868,8 @@ static int files_fsck(struct ref_store *ref_store,
38683868
struct files_ref_store *refs =
38693869
files_downcast(ref_store, REF_STORE_READ, "fsck");
38703870

3871-
return files_fsck_refs(ref_store, o, wt) |
3872-
refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt);
3871+
return refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt) |
3872+
files_fsck_refs(ref_store, o, wt);
38733873
}
38743874

38753875
struct ref_storage_be refs_be_files = {

refs/packed-backend.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,12 +1749,100 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
17491749
return empty_ref_iterator_begin();
17501750
}
17511751

1752+
static int packed_fsck_ref_next_line(struct fsck_options *o,
1753+
int line_number, const char *start,
1754+
const char *eof, const char **eol)
1755+
{
1756+
int ret = 0;
1757+
1758+
*eol = memchr(start, '\n', eof - start);
1759+
if (!*eol) {
1760+
struct strbuf packed_entry = STRBUF_INIT;
1761+
struct fsck_ref_report report = { 0 };
1762+
1763+
strbuf_addf(&packed_entry, "packed-refs line %d", line_number);
1764+
report.path = packed_entry.buf;
1765+
ret = fsck_report_ref(o, &report,
1766+
FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
1767+
"'%.*s' is not terminated with a newline",
1768+
(int)(eof - start), start);
1769+
1770+
/*
1771+
* There is no newline but we still want to parse it to the end of
1772+
* the buffer.
1773+
*/
1774+
*eol = eof;
1775+
strbuf_release(&packed_entry);
1776+
}
1777+
1778+
return ret;
1779+
}
1780+
1781+
static int packed_fsck_ref_header(struct fsck_options *o, const char *start, const char *eol)
1782+
{
1783+
const char *err_fmt = NULL;
1784+
int fsck_msg_id = -1;
1785+
1786+
if (!starts_with(start, "# pack-refs with:")) {
1787+
err_fmt = "'%.*s' does not start with '# pack-refs with:'";
1788+
fsck_msg_id = FSCK_MSG_BAD_PACKED_REF_HEADER;
1789+
} else if (strncmp(start, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER))) {
1790+
err_fmt = "'%.*s' is not the official packed-refs header";
1791+
fsck_msg_id = FSCK_MSG_UNKNOWN_PACKED_REF_HEADER;
1792+
}
1793+
1794+
if (err_fmt && fsck_msg_id >= 0) {
1795+
struct fsck_ref_report report = { 0 };
1796+
report.path = "packed-refs.header";
1797+
1798+
return fsck_report_ref(o, &report, fsck_msg_id, err_fmt,
1799+
(int)(eol - start), start);
1800+
1801+
}
1802+
1803+
return 0;
1804+
}
1805+
1806+
static int packed_fsck_ref_content(struct fsck_options *o,
1807+
const char *start, const char *eof)
1808+
{
1809+
int line_number = 1;
1810+
const char *eol;
1811+
int ret = 0;
1812+
1813+
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
1814+
if (*start == '#') {
1815+
ret |= packed_fsck_ref_header(o, start, eol);
1816+
1817+
start = eol + 1;
1818+
line_number++;
1819+
} else {
1820+
struct fsck_ref_report report = { 0 };
1821+
report.path = "packed-refs";
1822+
1823+
ret |= fsck_report_ref(o, &report,
1824+
FSCK_MSG_PACKED_REF_MISSING_HEADER,
1825+
"missing header line");
1826+
}
1827+
1828+
/*
1829+
* If there is anything wrong during the parsing of the "packed-refs"
1830+
* file, we should not check the object of the refs.
1831+
*/
1832+
if (ret)
1833+
o->safe_object_check = 0;
1834+
1835+
1836+
return ret;
1837+
}
1838+
17521839
static int packed_fsck(struct ref_store *ref_store,
17531840
struct fsck_options *o,
17541841
struct worktree *wt)
17551842
{
17561843
struct packed_ref_store *refs = packed_downcast(ref_store,
17571844
REF_STORE_READ, "fsck");
1845+
struct strbuf packed_ref_content = STRBUF_INIT;
17581846
struct stat st;
17591847
int ret = 0;
17601848

@@ -1780,7 +1868,24 @@ static int packed_fsck(struct ref_store *ref_store,
17801868
goto cleanup;
17811869
}
17821870

1871+
if (strbuf_read_file(&packed_ref_content, refs->path, 0) < 0) {
1872+
/*
1873+
* Although we have checked that the file exists, there is a possibility
1874+
* that it has been removed between the lstat() and the read attempt by
1875+
* another process. In that case, we should not report an error.
1876+
*/
1877+
if (errno == ENOENT)
1878+
goto cleanup;
1879+
1880+
ret = error_errno("could not read %s", refs->path);
1881+
goto cleanup;
1882+
}
1883+
1884+
ret = packed_fsck_ref_content(o, packed_ref_content.buf,
1885+
packed_ref_content.buf + packed_ref_content.len);
1886+
17831887
cleanup:
1888+
strbuf_release(&packed_ref_content);
17841889
return ret;
17851890
}
17861891

t/t0602-reffiles-fsck.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,48 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
646646
test_cmp expect err
647647
'
648648

649+
test_expect_success 'packed-refs header should be checked' '
650+
test_when_finished "rm -rf repo" &&
651+
git init repo &&
652+
cd repo &&
653+
test_commit default &&
654+
655+
git refs verify 2>err &&
656+
test_must_be_empty err &&
657+
658+
printf "$(git rev-parse main) refs/heads/main\n" >.git/packed-refs &&
659+
git refs verify 2>err &&
660+
cat >expect <<-EOF &&
661+
warning: packed-refs: packedRefMissingHeader: missing header line
662+
EOF
663+
rm .git/packed-refs &&
664+
test_cmp expect err &&
665+
666+
for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
667+
"# pack-refs with traits: peeled fully-peeled sorted " \
668+
"# pack-refs with a: peeled fully-peeled"
669+
do
670+
printf "%s\n" "$bad_header" >.git/packed-refs &&
671+
test_must_fail git refs verify 2>err &&
672+
cat >expect <<-EOF &&
673+
error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with:'\''
674+
EOF
675+
rm .git/packed-refs &&
676+
test_cmp expect err || return 1
677+
done &&
678+
679+
for unknown_header in "# pack-refs with: peeled fully-peeled sorted garbage" \
680+
"# pack-refs with: peeled" \
681+
"# pack-refs with: peeled peeled-fully sort"
682+
do
683+
printf "%s\n" "$unknown_header" >.git/packed-refs &&
684+
git refs verify 2>err &&
685+
cat >expect <<-EOF &&
686+
warning: packed-refs.header: unknownPackedRefHeader: '\''$unknown_header'\'' is not the official packed-refs header
687+
EOF
688+
rm .git/packed-refs &&
689+
test_cmp expect err || return 1
690+
done
691+
'
692+
649693
test_done

0 commit comments

Comments
 (0)