aboutsummaryrefslogtreecommitdiffstats
path: root/refs/packed-backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs/packed-backend.c')
-rw-r--r--refs/packed-backend.c363
1 files changed, 351 insertions, 12 deletions
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..813e5020e4 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -299,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -321,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -493,6 +500,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -693,7 +715,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
@@ -894,6 +916,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
@@ -1748,15 +1773,329 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol,
+ unsigned int *sorted)
+{
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
+}
+
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned int *sorted,
+ const char *start, const char *eof)
+{
+ struct strbuf refname = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
+ return ret;
+}
+
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
+ struct stat st;
+ int ret = 0;
+ int fd = -1;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file but a symlink");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open '%s'"), refs->path);
+ goto cleanup;
+ } else if (fstat(fd, &st) < 0) {
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ } else if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read '%s'"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ strbuf_release(&packed_ref_content);
+ return ret;
}
struct ref_storage_be refs_be_packed = {