summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2015-05-11 14:23:42 -0700
committerJunio C Hamano <gitster@pobox.com>2015-05-11 14:23:42 -0700
commit6cc983d0adc9fca975d977b4432c4645db9de464 (patch)
treeeb4c7e9af146935175ce09f0568a9a6b31b5a331
parent66ff763ebb35e5fc379f73c2769656cd61fe6b33 (diff)
parenta33729267504c7b1a63a1f05b47faea6f9e1642c (diff)
downloadgit-6cc983d0adc9fca975d977b4432c4645db9de464.tar.gz
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
-rw-r--r--Makefile6
-rw-r--r--config.c4
-rw-r--r--config.mak.uname1
-rw-r--r--git-compat-util.h6
-rw-r--r--refs.c6
-rw-r--r--strbuf.c49
-rw-r--r--strbuf.h3
-rwxr-xr-xt/t1430-bad-ref-name.sh8
8 files changed, 77 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index 5f3987fe3b..36655d5a16 100644
--- a/Makefile
+++ b/Makefile
@@ -359,6 +359,8 @@ all::
# compiler is detected to support it.
#
# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
+#
+# Define HAVE_GETDELIM if your system has the getdelim() function.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1437,6 +1439,10 @@ ifdef HAVE_BSD_SYSCTL
BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
endif
+ifdef HAVE_GETDELIM
+ BASIC_CFLAGS += -DHAVE_GETDELIM
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK = NoThanks
endif
diff --git a/config.c b/config.c
index c4424c0138..6d0098fc80 100644
--- a/config.c
+++ b/config.c
@@ -50,7 +50,7 @@ static struct config_set the_config_set;
static int config_file_fgetc(struct config_source *conf)
{
- return fgetc(conf->u.file);
+ return getc_unlocked(conf->u.file);
}
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)
f = fopen(filename, "r");
if (f) {
+ flockfile(f);
ret = do_config_from_file(fn, filename, filename, f, data);
+ funlockfile(f);
fclose(f);
}
return ret;
diff --git a/config.mak.uname b/config.mak.uname
index f4e77cb9e5..d26665fa54 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -36,6 +36,7 @@ ifeq ($(uname_S),Linux)
HAVE_DEV_TTY = YesPlease
HAVE_CLOCK_GETTIME = YesPlease
HAVE_CLOCK_MONOTONIC = YesPlease
+ HAVE_GETDELIM = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_ALLOCA_H = YesPlease
diff --git a/git-compat-util.h b/git-compat-util.h
index bc8fc8cf85..685a0a4063 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -883,4 +883,10 @@ struct tm *git_gmtime_r(const time_t *, struct tm *);
# define SHELL_PATH "/bin/sh"
#endif
+#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
+#define flockfile(fh)
+#define funlockfile(fh)
+#define getc_unlocked(fh) getc(fh)
+#endif
+
#endif
diff --git a/refs.c b/refs.c
index 0312f05257..81a455b807 100644
--- a/refs.c
+++ b/refs.c
@@ -344,8 +344,6 @@ static struct ref_entry *create_ref_entry(const char *refname,
if (check_name &&
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die("Reference has invalid format: '%s'", refname);
- if (!check_name && !refname_is_safe(refname))
- die("Reference has invalid name: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
hashcpy(ref->u.value.sha1, sha1);
@@ -1178,6 +1176,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
int flag = REF_ISPACKED;
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname))
+ die("packed refname is dangerous: %s", refname);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
@@ -1323,6 +1323,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
}
if (check_refname_format(refname.buf,
REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname.buf))
+ die("loose refname is dangerous: %s", refname.buf);
hashclr(sha1);
flag |= REF_BAD_NAME | REF_ISBROKEN;
}
diff --git a/strbuf.c b/strbuf.c
index 88cafd4a70..0d4f4e54ec 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -435,6 +435,47 @@ int strbuf_getcwd(struct strbuf *sb)
return -1;
}
+#ifdef HAVE_GETDELIM
+int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
+{
+ ssize_t r;
+
+ if (feof(fp))
+ return EOF;
+
+ strbuf_reset(sb);
+
+ /* Translate slopbuf to NULL, as we cannot call realloc on it */
+ if (!sb->alloc)
+ sb->buf = NULL;
+ r = getdelim(&sb->buf, &sb->alloc, term, fp);
+
+ if (r > 0) {
+ sb->len = r;
+ return 0;
+ }
+ assert(r == -1);
+
+ /*
+ * Normally we would have called xrealloc, which will try to free
+ * memory and recover. But we have no way to tell getdelim() to do so.
+ * Worse, we cannot try to recover ENOMEM ourselves, because we have
+ * no idea how many bytes were read by getdelim.
+ *
+ * Dying here is reasonable. It mirrors what xrealloc would do on
+ * catastrophic memory failure. We skip the opportunity to free pack
+ * memory and retry, but that's unlikely to help for a malloc small
+ * enough to hold a single line of input, anyway.
+ */
+ if (errno == ENOMEM)
+ die("Out of memory, getdelim failed");
+
+ /* Restore slopbuf that we moved out of the way before */
+ if (!sb->buf)
+ strbuf_init(sb, 0);
+ return EOF;
+}
+#else
int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
@@ -443,18 +484,22 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
return EOF;
strbuf_reset(sb);
- while ((ch = fgetc(fp)) != EOF) {
- strbuf_grow(sb, 1);
+ flockfile(fp);
+ while ((ch = getc_unlocked(fp)) != EOF) {
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
if (ch == term)
break;
}
+ funlockfile(fp);
if (ch == EOF && sb->len == 0)
return EOF;
sb->buf[sb->len] = '\0';
return 0;
}
+#endif
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
{
diff --git a/strbuf.h b/strbuf.h
index 1883494ca3..01c5c6371b 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -205,7 +205,8 @@ extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
*/
static inline void strbuf_addch(struct strbuf *sb, int c)
{
- strbuf_grow(sb, 1);
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index 468e85621a..16d0b8bd1a 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -68,6 +68,14 @@ test_expect_success 'branch -D cannot delete non-ref in .git dir' '
test_cmp expect .git/my-private-file
'
+test_expect_success 'branch -D cannot delete ref in .git dir' '
+ git rev-parse HEAD >.git/my-private-file &&
+ git rev-parse HEAD >expect &&
+ git branch foo/legit &&
+ test_must_fail git branch -D foo////./././../../../my-private-file &&
+ test_cmp expect .git/my-private-file
+'
+
test_expect_success 'branch -D cannot delete absolute path' '
git branch -f extra &&
test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" &&