Skip to content

Commit

Permalink
Merge branch 'sj/ref-contents-check' into seen
Browse files Browse the repository at this point in the history
* sj/ref-contents-check:
  ref: add symlink ref consistency check for files backend
  ref: add symbolic ref content check for files backend
  ref: add regular ref content check for files backend
  fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro
  • Loading branch information
gitster committed Aug 20, 2024
2 parents ec184e3 + e53df4d commit a727640
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 5 deletions.
12 changes: 12 additions & 0 deletions Documentation/fsck-msgids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.

`badRefContent`::
(ERROR) A ref has a bad content.

`badRefFiletype`::
(ERROR) A ref has a bad file type.

`badRefName`::
(ERROR) A ref has an invalid format.

`badSymrefPointee`::
(ERROR) The pointee of a symref is bad.

`badTagName`::
(INFO) A tag has an invalid format.

Expand Down Expand Up @@ -170,6 +176,12 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.

`refMissingNewline`::
(WARN) A valid ref does not end with newline.

`trailingRefContent`::
(WARN) A ref has trailing contents.

`treeNotSorted`::
(ERROR) A tree is not properly sorted.

Expand Down
10 changes: 10 additions & 0 deletions fsck.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ enum fsck_msg_type {
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
FUNC(BAD_REF_NAME, ERROR) \
FUNC(BAD_SYMREF_POINTEE, ERROR) \
FUNC(BAD_TIMEZONE, ERROR) \
FUNC(BAD_TREE, ERROR) \
FUNC(BAD_TREE_SHA1, ERROR) \
Expand Down Expand Up @@ -73,6 +75,8 @@ enum fsck_msg_type {
FUNC(HAS_DOTDOT, WARN) \
FUNC(HAS_DOTGIT, WARN) \
FUNC(NULL_SHA1, WARN) \
FUNC(REF_MISSING_NEWLINE, WARN) \
FUNC(TRAILING_REF_CONTENT, WARN) \
FUNC(ZERO_PADDED_FILEMODE, WARN) \
FUNC(NUL_IN_COMMIT, WARN) \
FUNC(LARGE_PATHNAME, WARN) \
Expand Down Expand Up @@ -152,6 +156,12 @@ struct fsck_ref_report {
const char *referent;
};

#define FSCK_REF_REPORT_DEFAULT { \
.path = NULL, \
.oid = NULL, \
.referent = NULL, \
}

struct fsck_options {
fsck_walk_func walk;
fsck_error error_func;
Expand Down
2 changes: 1 addition & 1 deletion refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
}

result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
oid, referent, type, failure_errno);
oid, referent, type, NULL, failure_errno);

done:
strbuf_release(&full_path);
Expand Down
188 changes: 185 additions & 3 deletions refs/files-backend.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "../git-compat-util.h"
#include "../abspath.h"
#include "../copy.h"
#include "../environment.h"
#include "../gettext.h"
Expand Down Expand Up @@ -560,7 +561,7 @@ static int read_ref_internal(struct ref_store *ref_store, const char *refname,
buf = sb_contents.buf;

ret = parse_loose_ref_contents(ref_store->repo->hash_algo, buf,
oid, referent, type, &myerr);
oid, referent, type, NULL, &myerr);

out:
if (ret && !myerr)
Expand Down Expand Up @@ -597,7 +598,7 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
int parse_loose_ref_contents(const struct git_hash_algo *algop,
const char *buf, struct object_id *oid,
struct strbuf *referent, unsigned int *type,
int *failure_errno)
const char **trailing, int *failure_errno)
{
const char *p;
if (skip_prefix(buf, "ref:", &buf)) {
Expand All @@ -619,6 +620,10 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
*failure_errno = EINVAL;
return -1;
}

if (trailing)
*trailing = p;

return 0;
}

Expand Down Expand Up @@ -3428,6 +3433,182 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
const char *refs_check_dir,
struct dir_iterator *iter);

/*
* Check the symref "pointee_name" and "pointee_path". The caller should
* make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
* would be the content after "refs:". For symblic link, "pointee_name" would
* be the relative path agaignst "gitdir".
*/
static int files_fsck_symref_target(struct fsck_options *o,
struct fsck_ref_report *report,
const char *refname,
struct strbuf *pointee_name,
struct strbuf *pointee_path,
unsigned int symbolic_link)
{
unsigned int newline_num = 0;
unsigned int space_num = 0;
const char *p = NULL;
struct stat st;
int ret = 0;

if (!skip_prefix(pointee_name->buf, "refs/", &p)) {

ret = fsck_report_ref(o, report,
FSCK_MSG_BAD_SYMREF_POINTEE,
"points to ref outside the refs directory");
goto out;
}

if (!symbolic_link) {
while (*p != '\0') {
if ((space_num || newline_num) && !isspace(*p)) {
ret = fsck_report_ref(o, report,
FSCK_MSG_BAD_REF_CONTENT,
"contains non-null garbage");
goto out;
}

if (*p == '\n') {
newline_num++;
} else if (*p == ' ') {
space_num++;
}
p++;
}

if (space_num || newline_num > 1) {
ret = fsck_report_ref(o, report,
FSCK_MSG_TRAILING_REF_CONTENT,
"trailing null-garbage");
} else if (!newline_num) {
ret = fsck_report_ref(o, report,
FSCK_MSG_REF_MISSING_NEWLINE,
"missing newline");
}

strbuf_rtrim(pointee_name);
}

if (check_refname_format(pointee_name->buf, 0)) {
ret = fsck_report_ref(o, report,
FSCK_MSG_BAD_SYMREF_POINTEE,
"points to refname with invalid format");
}

/*
* Missing target should not be treated as any error worthy event and
* not even warn. It is a common case that a symbolic ref points to a
* ref that does not exist yet. If the target ref does not exist, just
* skip the check for the file type.
*/
if (lstat(pointee_path->buf, &st) < 0)
goto out;

if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
ret = fsck_report_ref(o, report,
FSCK_MSG_BAD_SYMREF_POINTEE,
"points to an invalid file type");
goto out;
}

out:
return ret;
}

static int files_fsck_refs_content(struct ref_store *ref_store,
struct fsck_options *o,
const char *refs_check_dir,
struct dir_iterator *iter)
{
struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
struct strbuf pointee_path = STRBUF_INIT;
struct strbuf ref_content = STRBUF_INIT;
struct strbuf abs_gitdir = STRBUF_INIT;
struct strbuf referent = STRBUF_INIT;
struct strbuf refname = STRBUF_INIT;
unsigned int symbolic_link = 0;
const char *trailing = NULL;
unsigned int type = 0;
int failure_errno = 0;
struct object_id oid;
int ret = 0;

strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
report.path = refname.buf;

if (S_ISREG(iter->st.st_mode)) {
if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
ret = error_errno(_("%s/%s: unable to read the ref"),
refs_check_dir, iter->relative_path);
goto cleanup;
}

if (parse_loose_ref_contents(ref_store->repo->hash_algo,
ref_content.buf, &oid, &referent,
&type, &trailing, &failure_errno)) {
ret = fsck_report_ref(o, &report,
FSCK_MSG_BAD_REF_CONTENT,
"invalid ref content");
goto cleanup;
}

if (!(type & REF_ISSYMREF)) {
if (*trailing == '\0') {
ret = fsck_report_ref(o, &report,
FSCK_MSG_REF_MISSING_NEWLINE,
"missing newline");
goto cleanup;
}

if (*trailing != '\n' || (*(trailing + 1) != '\0')) {
ret = fsck_report_ref(o, &report,
FSCK_MSG_TRAILING_REF_CONTENT,
"trailing garbage in ref");
goto cleanup;
}
} else {
strbuf_addf(&pointee_path, "%s/%s",
ref_store->gitdir, referent.buf);
ret = files_fsck_symref_target(o, &report, refname.buf,
&referent,
&pointee_path,
symbolic_link);
}
} else if (S_ISLNK(iter->st.st_mode)) {
const char *pointee_name = NULL;

symbolic_link = 1;

strbuf_add_real_path(&pointee_path, iter->path.buf);
strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
strbuf_normalize_path(&abs_gitdir);
if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
strbuf_addch(&abs_gitdir, '/');

if (!skip_prefix(pointee_path.buf,
abs_gitdir.buf, &pointee_name)) {
ret = fsck_report_ref(o, &report,
FSCK_MSG_BAD_SYMREF_POINTEE,
"point to target outside gitdir");
goto cleanup;
}

strbuf_addstr(&referent, pointee_name);
ret = files_fsck_symref_target(o, &report, refname.buf,
&referent, &pointee_path,
symbolic_link);
}

cleanup:
strbuf_release(&refname);
strbuf_release(&ref_content);
strbuf_release(&referent);
strbuf_release(&pointee_path);
strbuf_release(&abs_gitdir);
return ret;
}

static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
struct fsck_options *o,
const char *refs_check_dir,
Expand All @@ -3444,7 +3625,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
goto cleanup;

if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
struct fsck_ref_report report = { .path = NULL };
struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;

strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
report.path = sb.buf;
Expand Down Expand Up @@ -3510,6 +3691,7 @@ static int files_fsck_refs(struct ref_store *ref_store,
{
files_fsck_refs_fn fsck_refs_fn[]= {
files_fsck_refs_name,
files_fsck_refs_content,
NULL,
};

Expand Down
2 changes: 1 addition & 1 deletion refs/refs-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ struct ref_store {
int parse_loose_ref_contents(const struct git_hash_algo *algop,
const char *buf, struct object_id *oid,
struct strbuf *referent, unsigned int *type,
int *failure_errno);
const char **trailing, int *failure_errno);

/*
* Fill in the generic part of refs and add it to our collection of
Expand Down
Loading

0 comments on commit a727640

Please sign in to comment.