transport-helper.c: do not overwrite forced bit
[gitweb.git] / config.c
index d04e8157abc415c6956bb8a237c8a17bd9624f8c..e1d66a145b756c49c4e4902200c354499532a428 100644 (file)
--- a/config.c
+++ b/config.c
 #include "strbuf.h"
 #include "quote.h"
 
-typedef struct config_file {
-       struct config_file *prev;
-       FILE *f;
+struct config_source {
+       struct config_source *prev;
+       union {
+               FILE *file;
+               struct config_buf {
+                       const char *buf;
+                       size_t len;
+                       size_t pos;
+               } buf;
+       } u;
        const char *name;
+       int die_on_error;
        int linenr;
        int eof;
        struct strbuf value;
        struct strbuf var;
-} config_file;
 
-static config_file *cf;
+       int (*do_fgetc)(struct config_source *c);
+       int (*do_ungetc)(int c, struct config_source *conf);
+       long (*do_ftell)(struct config_source *c);
+};
+
+static struct config_source *cf;
 
 static int zlib_compression_seen;
 
+static int config_file_fgetc(struct config_source *conf)
+{
+       return fgetc(conf->u.file);
+}
+
+static int config_file_ungetc(int c, struct config_source *conf)
+{
+       return ungetc(c, conf->u.file);
+}
+
+static long config_file_ftell(struct config_source *conf)
+{
+       return ftell(conf->u.file);
+}
+
+
+static int config_buf_fgetc(struct config_source *conf)
+{
+       if (conf->u.buf.pos < conf->u.buf.len)
+               return conf->u.buf.buf[conf->u.buf.pos++];
+
+       return EOF;
+}
+
+static int config_buf_ungetc(int c, struct config_source *conf)
+{
+       if (conf->u.buf.pos > 0)
+               return conf->u.buf.buf[--conf->u.buf.pos];
+
+       return EOF;
+}
+
+static long config_buf_ftell(struct config_source *conf)
+{
+       return conf->u.buf.pos;
+}
+
 #define MAX_INCLUDE_DEPTH 10
 static const char include_depth_advice[] =
 "exceeded maximum include depth (%d) while including\n"
@@ -168,27 +217,22 @@ int git_config_from_parameters(config_fn_t fn, void *data)
 
 static int get_next_char(void)
 {
-       int c;
-       FILE *f;
-
-       c = '\n';
-       if (cf && ((f = cf->f) != NULL)) {
-               c = fgetc(f);
-               if (c == '\r') {
-                       /* DOS like systems */
-                       c = fgetc(f);
-                       if (c != '\n') {
-                               ungetc(c, f);
-                               c = '\r';
-                       }
-               }
-               if (c == '\n')
-                       cf->linenr++;
-               if (c == EOF) {
-                       cf->eof = 1;
-                       c = '\n';
+       int c = cf->do_fgetc(cf);
+
+       if (c == '\r') {
+               /* DOS like systems */
+               c = cf->do_fgetc(cf);
+               if (c != '\n') {
+                       cf->do_ungetc(c, cf);
+                       c = '\r';
                }
        }
+       if (c == '\n')
+               cf->linenr++;
+       if (c == EOF) {
+               cf->eof = 1;
+               c = '\n';
+       }
        return c;
 }
 
@@ -339,7 +383,7 @@ static int get_base_var(struct strbuf *name)
        }
 }
 
-static int git_parse_file(config_fn_t fn, void *data)
+static int git_parse_source(config_fn_t fn, void *data)
 {
        int comment = 0;
        int baselen = 0;
@@ -399,7 +443,10 @@ static int git_parse_file(config_fn_t fn, void *data)
                if (get_value(fn, data, var) < 0)
                        break;
        }
-       die("bad config file line %d in %s", cf->linenr, cf->name);
+       if (cf->die_on_error)
+               die("bad config file line %d in %s", cf->linenr, cf->name);
+       else
+               return error("bad config file line %d in %s", cf->linenr, cf->name);
 }
 
 static int parse_unit_factor(const char *end, uintmax_t *val)
@@ -421,7 +468,7 @@ static int parse_unit_factor(const char *end, uintmax_t *val)
        return 0;
 }
 
-static int git_parse_long(const char *value, long *ret)
+static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
 {
        if (value && *value) {
                char *end;
@@ -433,21 +480,25 @@ static int git_parse_long(const char *value, long *ret)
                val = strtoimax(value, &end, 0);
                if (errno == ERANGE)
                        return 0;
-               if (!parse_unit_factor(end, &factor))
+               if (!parse_unit_factor(end, &factor)) {
+                       errno = EINVAL;
                        return 0;
+               }
                uval = abs(val);
                uval *= factor;
-               if ((uval > maximum_signed_value_of_type(long)) ||
-                   (abs(val) > uval))
+               if (uval > max || abs(val) > uval) {
+                       errno = ERANGE;
                        return 0;
+               }
                val *= factor;
                *ret = val;
                return 1;
        }
+       errno = EINVAL;
        return 0;
 }
 
-int git_parse_ulong(const char *value, unsigned long *ret)
+static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
 {
        if (value && *value) {
                char *end;
@@ -459,29 +510,75 @@ int git_parse_ulong(const char *value, unsigned long *ret)
                if (errno == ERANGE)
                        return 0;
                oldval = val;
-               if (!parse_unit_factor(end, &val))
+               if (!parse_unit_factor(end, &val)) {
+                       errno = EINVAL;
                        return 0;
-               if ((val > maximum_unsigned_value_of_type(long)) ||
-                   (oldval > val))
+               }
+               if (val > max || oldval > val) {
+                       errno = ERANGE;
                        return 0;
+               }
                *ret = val;
                return 1;
        }
+       errno = EINVAL;
        return 0;
 }
 
-static void die_bad_config(const char *name)
+static int git_parse_int(const char *value, int *ret)
+{
+       intmax_t tmp;
+       if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int)))
+               return 0;
+       *ret = tmp;
+       return 1;
+}
+
+static int git_parse_int64(const char *value, int64_t *ret)
+{
+       intmax_t tmp;
+       if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t)))
+               return 0;
+       *ret = tmp;
+       return 1;
+}
+
+int git_parse_ulong(const char *value, unsigned long *ret)
+{
+       uintmax_t tmp;
+       if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long)))
+               return 0;
+       *ret = tmp;
+       return 1;
+}
+
+static void die_bad_number(const char *name, const char *value)
 {
+       const char *reason = errno == ERANGE ?
+                            "out of range" :
+                            "invalid unit";
+       if (!value)
+               value = "";
+
        if (cf && cf->name)
-               die("bad config value for '%s' in %s", name, cf->name);
-       die("bad config value for '%s'", name);
+               die("bad numeric config value '%s' for '%s' in %s: %s",
+                   value, name, cf->name, reason);
+       die("bad numeric config value '%s' for '%s': %s", value, name, reason);
 }
 
 int git_config_int(const char *name, const char *value)
 {
-       long ret = 0;
-       if (!git_parse_long(value, &ret))
-               die_bad_config(name);
+       int ret;
+       if (!git_parse_int(value, &ret))
+               die_bad_number(name, value);
+       return ret;
+}
+
+int64_t git_config_int64(const char *name, const char *value)
+{
+       int64_t ret;
+       if (!git_parse_int64(value, &ret))
+               die_bad_number(name, value);
        return ret;
 }
 
@@ -489,7 +586,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
 {
        unsigned long ret;
        if (!git_parse_ulong(value, &ret))
-               die_bad_config(name);
+               die_bad_number(name, value);
        return ret;
 }
 
@@ -512,10 +609,10 @@ static int git_config_maybe_bool_text(const char *name, const char *value)
 
 int git_config_maybe_bool(const char *name, const char *value)
 {
-       long v = git_config_maybe_bool_text(name, value);
+       int v = git_config_maybe_bool_text(name, value);
        if (0 <= v)
                return v;
-       if (git_parse_long(value, &v))
+       if (git_parse_int(value, &v))
                return !!v;
        return -1;
 }
@@ -906,6 +1003,33 @@ int git_default_config(const char *var, const char *value, void *dummy)
        return 0;
 }
 
+/*
+ * All source specific fields in the union, die_on_error, name and the callbacks
+ * fgetc, ungetc, ftell of top need to be initialized before calling
+ * this function.
+ */
+static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
+{
+       int ret;
+
+       /* push config-file parsing state stack */
+       top->prev = cf;
+       top->linenr = 1;
+       top->eof = 0;
+       strbuf_init(&top->value, 1024);
+       strbuf_init(&top->var, 1024);
+       cf = top;
+
+       ret = git_parse_source(fn, data);
+
+       /* pop config-file parsing state stack */
+       strbuf_release(&top->value);
+       strbuf_release(&top->var);
+       cf = top->prev;
+
+       return ret;
+}
+
 int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 {
        int ret;
@@ -913,30 +1037,74 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 
        ret = -1;
        if (f) {
-               config_file top;
+               struct config_source top;
 
-               /* push config-file parsing state stack */
-               top.prev = cf;
-               top.f = f;
+               top.u.file = f;
                top.name = filename;
-               top.linenr = 1;
-               top.eof = 0;
-               strbuf_init(&top.value, 1024);
-               strbuf_init(&top.var, 1024);
-               cf = &top;
-
-               ret = git_parse_file(fn, data);
+               top.die_on_error = 1;
+               top.do_fgetc = config_file_fgetc;
+               top.do_ungetc = config_file_ungetc;
+               top.do_ftell = config_file_ftell;
 
-               /* pop config-file parsing state stack */
-               strbuf_release(&top.value);
-               strbuf_release(&top.var);
-               cf = top.prev;
+               ret = do_config_from(&top, fn, data);
 
                fclose(f);
        }
        return ret;
 }
 
+int git_config_from_buf(config_fn_t fn, const char *name, const char *buf,
+                       size_t len, void *data)
+{
+       struct config_source top;
+
+       top.u.buf.buf = buf;
+       top.u.buf.len = len;
+       top.u.buf.pos = 0;
+       top.name = name;
+       top.die_on_error = 0;
+       top.do_fgetc = config_buf_fgetc;
+       top.do_ungetc = config_buf_ungetc;
+       top.do_ftell = config_buf_ftell;
+
+       return do_config_from(&top, fn, data);
+}
+
+static int git_config_from_blob_sha1(config_fn_t fn,
+                                    const char *name,
+                                    const unsigned char *sha1,
+                                    void *data)
+{
+       enum object_type type;
+       char *buf;
+       unsigned long size;
+       int ret;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("unable to load config blob object '%s'", name);
+       if (type != OBJ_BLOB) {
+               free(buf);
+               return error("reference '%s' does not point to a blob", name);
+       }
+
+       ret = git_config_from_buf(fn, name, buf, size, data);
+       free(buf);
+
+       return ret;
+}
+
+static int git_config_from_blob_ref(config_fn_t fn,
+                                   const char *name,
+                                   void *data)
+{
+       unsigned char sha1[20];
+
+       if (get_sha1(name, sha1) < 0)
+               return error("unable to resolve config blob '%s'", name);
+       return git_config_from_blob_sha1(fn, name, sha1, data);
+}
+
 const char *git_etc_gitconfig(void)
 {
        static const char *system_wide;
@@ -1002,7 +1170,9 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 }
 
 int git_config_with_options(config_fn_t fn, void *data,
-                           const char *filename, int respect_includes)
+                           const char *filename,
+                           const char *blob_ref,
+                           int respect_includes)
 {
        char *repo_config = NULL;
        int ret;
@@ -1021,6 +1191,8 @@ int git_config_with_options(config_fn_t fn, void *data,
         */
        if (filename)
                return git_config_from_file(fn, filename, data);
+       else if (blob_ref)
+               return git_config_from_blob_ref(fn, blob_ref, data);
 
        repo_config = git_pathdup("config");
        ret = git_config_early(fn, data, repo_config);
@@ -1031,7 +1203,7 @@ int git_config_with_options(config_fn_t fn, void *data,
 
 int git_config(config_fn_t fn, void *data)
 {
-       return git_config_with_options(fn, data, NULL, 1);
+       return git_config_with_options(fn, data, NULL, NULL, 1);
 }
 
 /*
@@ -1063,7 +1235,6 @@ static int store_aux(const char *key, const char *value, void *cb)
 {
        const char *ep;
        size_t section_len;
-       FILE *f = cf->f;
 
        switch (store.state) {
        case KEY_SEEN:
@@ -1075,7 +1246,7 @@ static int store_aux(const char *key, const char *value, void *cb)
                                return 1;
                        }
 
-                       store.offset[store.seen] = ftell(f);
+                       store.offset[store.seen] = cf->do_ftell(cf);
                        store.seen++;
                }
                break;
@@ -1102,19 +1273,19 @@ static int store_aux(const char *key, const char *value, void *cb)
                 * Do not increment matches: this is no match, but we
                 * just made sure we are in the desired section.
                 */
-               store.offset[store.seen] = ftell(f);
+               store.offset[store.seen] = cf->do_ftell(cf);
                /* fallthru */
        case SECTION_END_SEEN:
        case START:
                if (matches(key, value)) {
-                       store.offset[store.seen] = ftell(f);
+                       store.offset[store.seen] = cf->do_ftell(cf);
                        store.state = KEY_SEEN;
                        store.seen++;
                } else {
                        if (strrchr(key, '.') - key == store.baselen &&
                              !strncmp(key, store.key, store.baselen)) {
                                        store.state = SECTION_SEEN;
-                                       store.offset[store.seen] = ftell(f);
+                                       store.offset[store.seen] = cf->do_ftell(cf);
                        }
                }
        }