Skip to content

Commit 6cc983d

Browse files
committed
Merge branch 'jk/reading-packed-refs'
An earlier rewrite to use strbuf_getwholeline() instead of fgets(3) to read packed-refs file revealed that the former is unacceptably inefficient. * jk/reading-packed-refs: t1430: add another refs-escape test read_packed_refs: avoid double-checking sane refs strbuf_getwholeline: use getdelim if it is available strbuf_getwholeline: avoid calling strbuf_grow strbuf_addch: avoid calling strbuf_grow config: use getc_unlocked when reading from file strbuf_getwholeline: use getc_unlocked git-compat-util: add fallbacks for unlocked stdio strbuf_getwholeline: use getc macro
2 parents 66ff763 + a337292 commit 6cc983d

File tree

8 files changed

+77
-6
lines changed

8 files changed

+77
-6
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@ all::
359359
# compiler is detected to support it.
360360
#
361361
# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
362+
#
363+
# Define HAVE_GETDELIM if your system has the getdelim() function.
362364

363365
GIT-VERSION-FILE: FORCE
364366
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1437,6 +1439,10 @@ ifdef HAVE_BSD_SYSCTL
14371439
BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
14381440
endif
14391441

1442+
ifdef HAVE_GETDELIM
1443+
BASIC_CFLAGS += -DHAVE_GETDELIM
1444+
endif
1445+
14401446
ifeq ($(TCLTK_PATH),)
14411447
NO_TCLTK = NoThanks
14421448
endif

config.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ static struct config_set the_config_set;
5050

5151
static int config_file_fgetc(struct config_source *conf)
5252
{
53-
return fgetc(conf->u.file);
53+
return getc_unlocked(conf->u.file);
5454
}
5555

5656
static int config_file_ungetc(int c, struct config_source *conf)
@@ -1088,7 +1088,9 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
10881088

10891089
f = fopen(filename, "r");
10901090
if (f) {
1091+
flockfile(f);
10911092
ret = do_config_from_file(fn, filename, filename, f, data);
1093+
funlockfile(f);
10921094
fclose(f);
10931095
}
10941096
return ret;

config.mak.uname

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ifeq ($(uname_S),Linux)
3636
HAVE_DEV_TTY = YesPlease
3737
HAVE_CLOCK_GETTIME = YesPlease
3838
HAVE_CLOCK_MONOTONIC = YesPlease
39+
HAVE_GETDELIM = YesPlease
3940
endif
4041
ifeq ($(uname_S),GNU/kFreeBSD)
4142
HAVE_ALLOCA_H = YesPlease

git-compat-util.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,4 +883,10 @@ struct tm *git_gmtime_r(const time_t *, struct tm *);
883883
# define SHELL_PATH "/bin/sh"
884884
#endif
885885

886+
#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
887+
#define flockfile(fh)
888+
#define funlockfile(fh)
889+
#define getc_unlocked(fh) getc(fh)
890+
#endif
891+
886892
#endif

refs.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,6 @@ static struct ref_entry *create_ref_entry(const char *refname,
344344
if (check_name &&
345345
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
346346
die("Reference has invalid format: '%s'", refname);
347-
if (!check_name && !refname_is_safe(refname))
348-
die("Reference has invalid name: '%s'", refname);
349347
len = strlen(refname) + 1;
350348
ref = xmalloc(sizeof(struct ref_entry) + len);
351349
hashcpy(ref->u.value.sha1, sha1);
@@ -1178,6 +1176,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
11781176
int flag = REF_ISPACKED;
11791177

11801178
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
1179+
if (!refname_is_safe(refname))
1180+
die("packed refname is dangerous: %s", refname);
11811181
hashclr(sha1);
11821182
flag |= REF_BAD_NAME | REF_ISBROKEN;
11831183
}
@@ -1323,6 +1323,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
13231323
}
13241324
if (check_refname_format(refname.buf,
13251325
REFNAME_ALLOW_ONELEVEL)) {
1326+
if (!refname_is_safe(refname.buf))
1327+
die("loose refname is dangerous: %s", refname.buf);
13261328
hashclr(sha1);
13271329
flag |= REF_BAD_NAME | REF_ISBROKEN;
13281330
}

strbuf.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,47 @@ int strbuf_getcwd(struct strbuf *sb)
435435
return -1;
436436
}
437437

438+
#ifdef HAVE_GETDELIM
439+
int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
440+
{
441+
ssize_t r;
442+
443+
if (feof(fp))
444+
return EOF;
445+
446+
strbuf_reset(sb);
447+
448+
/* Translate slopbuf to NULL, as we cannot call realloc on it */
449+
if (!sb->alloc)
450+
sb->buf = NULL;
451+
r = getdelim(&sb->buf, &sb->alloc, term, fp);
452+
453+
if (r > 0) {
454+
sb->len = r;
455+
return 0;
456+
}
457+
assert(r == -1);
458+
459+
/*
460+
* Normally we would have called xrealloc, which will try to free
461+
* memory and recover. But we have no way to tell getdelim() to do so.
462+
* Worse, we cannot try to recover ENOMEM ourselves, because we have
463+
* no idea how many bytes were read by getdelim.
464+
*
465+
* Dying here is reasonable. It mirrors what xrealloc would do on
466+
* catastrophic memory failure. We skip the opportunity to free pack
467+
* memory and retry, but that's unlikely to help for a malloc small
468+
* enough to hold a single line of input, anyway.
469+
*/
470+
if (errno == ENOMEM)
471+
die("Out of memory, getdelim failed");
472+
473+
/* Restore slopbuf that we moved out of the way before */
474+
if (!sb->buf)
475+
strbuf_init(sb, 0);
476+
return EOF;
477+
}
478+
#else
438479
int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
439480
{
440481
int ch;
@@ -443,18 +484,22 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
443484
return EOF;
444485

445486
strbuf_reset(sb);
446-
while ((ch = fgetc(fp)) != EOF) {
447-
strbuf_grow(sb, 1);
487+
flockfile(fp);
488+
while ((ch = getc_unlocked(fp)) != EOF) {
489+
if (!strbuf_avail(sb))
490+
strbuf_grow(sb, 1);
448491
sb->buf[sb->len++] = ch;
449492
if (ch == term)
450493
break;
451494
}
495+
funlockfile(fp);
452496
if (ch == EOF && sb->len == 0)
453497
return EOF;
454498

455499
sb->buf[sb->len] = '\0';
456500
return 0;
457501
}
502+
#endif
458503

459504
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
460505
{

strbuf.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
205205
*/
206206
static inline void strbuf_addch(struct strbuf *sb, int c)
207207
{
208-
strbuf_grow(sb, 1);
208+
if (!strbuf_avail(sb))
209+
strbuf_grow(sb, 1);
209210
sb->buf[sb->len++] = c;
210211
sb->buf[sb->len] = '\0';
211212
}

t/t1430-bad-ref-name.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ test_expect_success 'branch -D cannot delete non-ref in .git dir' '
6868
test_cmp expect .git/my-private-file
6969
'
7070

71+
test_expect_success 'branch -D cannot delete ref in .git dir' '
72+
git rev-parse HEAD >.git/my-private-file &&
73+
git rev-parse HEAD >expect &&
74+
git branch foo/legit &&
75+
test_must_fail git branch -D foo////./././../../../my-private-file &&
76+
test_cmp expect .git/my-private-file
77+
'
78+
7179
test_expect_success 'branch -D cannot delete absolute path' '
7280
git branch -f extra &&
7381
test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" &&

0 commit comments

Comments
 (0)