summaryrefslogtreecommitdiff
path: root/src/config_file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/config_file.c')
-rw-r--r--src/config_file.c695
1 files changed, 413 insertions, 282 deletions
diff --git a/src/config_file.c b/src/config_file.c
index dec952115..15c8de49c 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -15,6 +15,7 @@
#include "git2/sys/config.h"
#include "git2/types.h"
#include "strmap.h"
+#include "array.h"
#include <ctype.h>
#include <sys/types.h>
@@ -25,8 +26,18 @@ GIT__USE_STRMAP;
typedef struct cvar_t {
struct cvar_t *next;
git_config_entry *entry;
+ int included; /* whether this is part of [include] */
} cvar_t;
+typedef struct git_config_file_iter {
+ git_config_iterator parent;
+ git_strmap_iter iter;
+ cvar_t* next_var;
+} git_config_file_iter;
+
+/* Max depth for [include] directives */
+#define MAX_INCLUDE_DEPTH 10
+
#define CVAR_LIST_HEAD(list) ((list)->head)
#define CVAR_LIST_TAIL(list) ((list)->tail)
@@ -65,34 +76,37 @@ typedef struct cvar_t {
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
(iter) = (tmp))
+struct reader {
+ time_t file_mtime;
+ size_t file_size;
+ char *file_path;
+ git_buf buffer;
+ char *read_ptr;
+ int line_number;
+ int eof;
+};
+
typedef struct {
git_config_backend parent;
git_strmap *values;
- struct {
- git_buf buffer;
- char *read_ptr;
- int line_number;
- int eof;
- } reader;
+ git_array_t(struct reader) readers;
char *file_path;
- time_t file_mtime;
- size_t file_size;
git_config_level_t level;
} diskfile_backend;
-static int config_parse(diskfile_backend *cfg_file, git_config_level_t level);
-static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
+static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
+static int parse_variable(struct reader *reader, char **var_name, char **var_value);
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
static char *escape_value(const char *ptr);
-static void set_parse_error(diskfile_backend *backend, int col, const char *error_str)
+static void set_parse_error(struct reader *reader, int col, const char *error_str)
{
giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
- error_str, backend->file_path, backend->reader.line_number, col);
+ error_str, reader->file_path, reader->line_number, col);
}
static void cvar_free(cvar_t *var)
@@ -106,6 +120,18 @@ static void cvar_free(cvar_t *var)
git__free(var);
}
+static int cvar_length(cvar_t *var)
+{
+ int length = 0;
+
+ while (var) {
+ length++;
+ var = var->next;
+ }
+
+ return length;
+}
+
int git_config_file_normalize_section(char *start, char *end)
{
char *scan;
@@ -118,7 +144,7 @@ int git_config_file_normalize_section(char *start, char *end)
if (end && scan >= end)
break;
if (isalnum(*scan))
- *scan = tolower(*scan);
+ *scan = (char)tolower(*scan);
else if (*scan != '-' || scan == start)
return GIT_EINVALIDSPEC;
}
@@ -129,41 +155,6 @@ int git_config_file_normalize_section(char *start, char *end)
return 0;
}
-/* Take something the user gave us and make it nice for our hash function */
-static int normalize_name(const char *in, char **out)
-{
- char *name, *fdot, *ldot;
-
- assert(in && out);
-
- name = git__strdup(in);
- GITERR_CHECK_ALLOC(name);
-
- fdot = strchr(name, '.');
- ldot = strrchr(name, '.');
-
- if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
- goto invalid;
-
- /* Validate and downcase up to first dot and after last dot */
- if (git_config_file_normalize_section(name, fdot) < 0 ||
- git_config_file_normalize_section(ldot + 1, NULL) < 0)
- goto invalid;
-
- /* If there is a middle range, make sure it doesn't have newlines */
- while (fdot < ldot)
- if (*fdot++ == '\n')
- goto invalid;
-
- *out = name;
- return 0;
-
-invalid:
- git__free(name);
- giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
- return GIT_EINVALIDSPEC;
-}
-
static void free_vars(git_strmap *values)
{
cvar_t *var = NULL;
@@ -184,6 +175,7 @@ static void free_vars(git_strmap *values)
static int config_open(git_config_backend *cfg, git_config_level_t level)
{
int res;
+ struct reader *reader;
diskfile_backend *b = (diskfile_backend *)cfg;
b->level = level;
@@ -191,32 +183,52 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values);
- git_buf_init(&b->reader.buffer, 0);
+ git_array_init(b->readers);
+ reader = git_array_alloc(b->readers);
+ memset(reader, 0, sizeof(struct reader));
+
+ reader->file_path = git__strdup(b->file_path);
+ GITERR_CHECK_ALLOC(reader->file_path);
+
+ git_buf_init(&reader->buffer, 0);
res = git_futils_readbuffer_updated(
- &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL);
+ &reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
/* It's fine if the file doesn't exist */
if (res == GIT_ENOTFOUND)
return 0;
- if (res < 0 || (res = config_parse(b, level)) < 0) {
+ if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) {
free_vars(b->values);
b->values = NULL;
}
- git_buf_free(&b->reader.buffer);
+ reader = git_array_get(b->readers, 0);
+ git_buf_free(&reader->buffer);
return res;
}
static int config_refresh(git_config_backend *cfg)
{
- int res, updated = 0;
+ int res = 0, updated = 0, any_updated = 0;
diskfile_backend *b = (diskfile_backend *)cfg;
git_strmap *old_values;
+ struct reader *reader;
+ uint32_t i;
- res = git_futils_readbuffer_updated(
- &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);
- if (res < 0 || !updated)
+ for (i = 0; i < git_array_size(b->readers); i++) {
+ reader = git_array_get(b->readers, i);
+ res = git_futils_readbuffer_updated(
+ &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated);
+
+ if (res < 0)
+ return (res == GIT_ENOTFOUND) ? 0 : res;
+
+ if (updated)
+ any_updated = 1;
+ }
+
+ if (!any_updated)
return (res == GIT_ENOTFOUND) ? 0 : res;
/* need to reload - store old values and prep for reload */
@@ -224,74 +236,88 @@ static int config_refresh(git_config_backend *cfg)
b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values);
- if ((res = config_parse(b, b->level)) < 0) {
+ if ((res = config_parse(b, reader, b->level, 0)) < 0) {
free_vars(b->values);
b->values = old_values;
} else {
free_vars(old_values);
}
- git_buf_free(&b->reader.buffer);
+ git_buf_free(&reader->buffer);
return res;
}
static void backend_free(git_config_backend *_backend)
{
diskfile_backend *backend = (diskfile_backend *)_backend;
+ uint32_t i;
if (backend == NULL)
return;
+ for (i = 0; i < git_array_size(backend->readers); i++) {
+ struct reader *r = git_array_get(backend->readers, i);
+ git__free(r->file_path);
+ }
+ git_array_clear(backend->readers);
+
git__free(backend->file_path);
free_vars(backend->values);
git__free(backend);
}
-static int file_foreach(
- git_config_backend *backend,
- const char *regexp,
- int (*fn)(const git_config_entry *, void *),
- void *data)
+static void config_iterator_free(
+ git_config_iterator* iter)
{
- diskfile_backend *b = (diskfile_backend *)backend;
- cvar_t *var, *next_var;
- const char *key;
- regex_t regex;
- int result = 0;
+ git__free(iter);
+}
- if (!b->values)
- return 0;
+static int config_iterator_next(
+ git_config_entry **entry,
+ git_config_iterator *iter)
+{
+ git_config_file_iter *it = (git_config_file_iter *) iter;
+ diskfile_backend *b = (diskfile_backend *) it->parent.backend;
+ int err = 0;
+ cvar_t * var;
- if (regexp != NULL) {
- if ((result = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
- giterr_set_regex(&regex, result);
- regfree(&regex);
- return -1;
- }
+ if (it->next_var == NULL) {
+ err = git_strmap_next((void**) &var, &(it->iter), b->values);
+ } else {
+ var = it->next_var;
}
- git_strmap_foreach(b->values, key, var,
- for (; var != NULL; var = next_var) {
- next_var = CVAR_LIST_NEXT(var);
+ if (err < 0) {
+ it->next_var = NULL;
+ return err;
+ }
- /* skip non-matching keys if regexp was provided */
- if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
- continue;
+ *entry = var->entry;
+ it->next_var = CVAR_LIST_NEXT(var);
- /* abort iterator on non-zero return value */
- if (fn(var->entry, data)) {
- giterr_clear();
- result = GIT_EUSER;
- goto cleanup;
- }
- }
- );
+ return 0;
+}
-cleanup:
- if (regexp != NULL)
- regfree(&regex);
+static int config_iterator_new(
+ git_config_iterator **iter,
+ struct git_config_backend* backend)
+{
+ diskfile_backend *b = (diskfile_backend *)backend;
+ git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter));
- return result;
+ GIT_UNUSED(b);
+
+ GITERR_CHECK_ALLOC(it);
+
+ it->parent.backend = backend;
+ it->iter = git_strmap_begin(b->values);
+ it->next_var = NULL;
+
+ it->parent.next = config_iterator_next;
+ it->parent.free = config_iterator_free;
+ *iter = (git_config_iterator *) it;
+
+ return 0;
}
static int config_set(git_config_backend *cfg, const char *name, const char *value)
@@ -302,7 +328,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val
khiter_t pos;
int rval, ret;
- if ((rval = normalize_name(name, &key)) < 0)
+ if ((rval = git_config__normalize_name(name, &key)) < 0)
return rval;
/*
@@ -359,10 +385,10 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val
GITERR_CHECK_ALLOC(esc_value);
}
- if (config_write(b, key, NULL, esc_value) < 0) {
+ if ((ret = config_write(b, key, NULL, esc_value)) < 0) {
git__free(esc_value);
cvar_free(var);
- return -1;
+ return ret;
}
git__free(esc_value);
@@ -384,82 +410,23 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
char *key;
khiter_t pos;
int error;
-
- if ((error = normalize_name(name, &key)) < 0)
- return error;
-
- pos = git_strmap_lookup_index(b->values, key);
- git__free(key);
-
- /* no error message; the config system will write one */
- if (!git_strmap_valid_index(b->values, pos))
- return GIT_ENOTFOUND;
-
- *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry;
-
- return 0;
-}
-
-static int config_get_multivar(
- git_config_backend *cfg,
- const char *name,
- const char *regex_str,
- int (*fn)(const git_config_entry *, void *),
- void *data)
-{
cvar_t *var;
- diskfile_backend *b = (diskfile_backend *)cfg;
- char *key;
- khiter_t pos;
- int error;
- if ((error = normalize_name(name, &key)) < 0)
+ if ((error = git_config__normalize_name(name, &key)) < 0)
return error;
pos = git_strmap_lookup_index(b->values, key);
git__free(key);
+ /* no error message; the config system will write one */
if (!git_strmap_valid_index(b->values, pos))
return GIT_ENOTFOUND;
var = git_strmap_value_at(b->values, pos);
+ while (var->next)
+ var = var->next;
- if (regex_str != NULL) {
- regex_t regex;
- int result;
-
- /* regex matching; build the regex */
- result = regcomp(&regex, regex_str, REG_EXTENDED);
- if (result < 0) {
- giterr_set_regex(&regex, result);
- regfree(&regex);
- return -1;
- }
-
- /* and throw the callback only on the variables that
- * match the regex */
- do {
- if (regexec(&regex, var->entry->value, 0, NULL, 0) == 0) {
- /* early termination by the user is not an error;
- * just break and return successfully */
- if (fn(var->entry, data) < 0)
- break;
- }
-
- var = var->next;
- } while (var != NULL);
- regfree(&regex);
- } else {
- /* no regex; go through all the variables */
- do {
- /* early termination by the user is not an error;
- * just break and return successfully */
- if (fn(var->entry, data) < 0)
- break;
-
- var = var->next;
- } while (var != NULL);
- }
+ *out = var->entry;
return 0;
}
@@ -477,7 +444,7 @@ static int config_set_multivar(
assert(regexp);
- if ((result = normalize_name(name, &key)) < 0)
+ if ((result = git_config__normalize_name(name, &key)) < 0)
return result;
pos = git_strmap_lookup_index(b->values, key);
@@ -550,7 +517,7 @@ static int config_delete(git_config_backend *cfg, const char *name)
int result;
khiter_t pos;
- if ((result = normalize_name(name, &key)) < 0)
+ if ((result = git_config__normalize_name(name, &key)) < 0)
return result;
pos = git_strmap_lookup_index(b->values, key);
@@ -576,6 +543,80 @@ static int config_delete(git_config_backend *cfg, const char *name)
return result;
}
+static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ cvar_t *var, *prev = NULL, *new_head = NULL;
+ cvar_t **to_delete;
+ int to_delete_idx;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ regex_t preg;
+ int result;
+ khiter_t pos;
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ pos = git_strmap_lookup_index(b->values, key);
+
+ if (!git_strmap_valid_index(b->values, pos)) {
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ git__free(key);
+ return GIT_ENOTFOUND;
+ }
+
+ var = git_strmap_value_at(b->values, pos);
+
+ result = regcomp(&preg, regexp, REG_EXTENDED);
+ if (result < 0) {
+ git__free(key);
+ giterr_set_regex(&preg, result);
+ regfree(&preg);
+ return -1;
+ }
+
+ to_delete = git__calloc(cvar_length(var), sizeof(cvar_t *));
+ GITERR_CHECK_ALLOC(to_delete);
+ to_delete_idx = 0;
+
+ while (var != NULL) {
+ cvar_t *next = var->next;
+
+ if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
+ // If we are past the head, reattach previous node to next one,
+ // otherwise set the new head for the strmap.
+ if (prev != NULL) {
+ prev->next = next;
+ } else {
+ new_head = next;
+ }
+
+ to_delete[to_delete_idx++] = var;
+ } else {
+ prev = var;
+ }
+
+ var = next;
+ }
+
+ if (new_head != NULL) {
+ git_strmap_set_value_at(b->values, pos, new_head);
+ } else {
+ git_strmap_delete_at(b->values, pos);
+ }
+
+ if (to_delete_idx > 0)
+ result = config_write(b, key, &preg, NULL);
+
+ while (to_delete_idx-- > 0)
+ cvar_free(to_delete[to_delete_idx]);
+
+ git__free(key);
+ git__free(to_delete);
+ regfree(&preg);
+ return result;
+}
+
int git_config_file__ondisk(git_config_backend **out, const char *path)
{
diskfile_backend *backend;
@@ -590,11 +631,11 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
backend->parent.open = config_open;
backend->parent.get = config_get;
- backend->parent.get_multivar = config_get_multivar;
backend->parent.set = config_set;
backend->parent.set_multivar = config_set_multivar;
backend->parent.del = config_delete;
- backend->parent.foreach = file_foreach;
+ backend->parent.del_multivar = config_delete_multivar;
+ backend->parent.iterator = config_iterator_new;
backend->parent.refresh = config_refresh;
backend->parent.free = backend_free;
@@ -603,26 +644,26 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
return 0;
}
-static int cfg_getchar_raw(diskfile_backend *cfg)
+static int reader_getchar_raw(struct reader *reader)
{
int c;
- c = *cfg->reader.read_ptr++;
+ c = *reader->read_ptr++;
/*
Win 32 line breaks: if we find a \r\n sequence,
return only the \n as a newline
*/
- if (c == '\r' && *cfg->reader.read_ptr == '\n') {
- cfg->reader.read_ptr++;
+ if (c == '\r' && *reader->read_ptr == '\n') {
+ reader->read_ptr++;
c = '\n';
}
if (c == '\n')
- cfg->reader.line_number++;
+ reader->line_number++;
if (c == 0) {
- cfg->reader.eof = 1;
+ reader->eof = 1;
c = '\n';
}
@@ -632,21 +673,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg)
#define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2)
-static int cfg_getchar(diskfile_backend *cfg_file, int flags)
+static int reader_getchar(struct reader *reader, int flags)
{
const int skip_whitespace = (flags & SKIP_WHITESPACE);
const int skip_comments = (flags & SKIP_COMMENTS);
int c;
- assert(cfg_file->reader.read_ptr);
+ assert(reader->read_ptr);
- do c = cfg_getchar_raw(cfg_file);
- while (skip_whitespace && git__isspace(c) &&
- !cfg_file->reader.eof);
+ do {
+ c = reader_getchar_raw(reader);
+ } while (skip_whitespace && git__isspace(c) &&
+ !reader->eof);
if (skip_comments && (c == '#' || c == ';')) {
- do c = cfg_getchar_raw(cfg_file);
- while (c != '\n');
+ do {
+ c = reader_getchar_raw(reader);
+ } while (c != '\n');
}
return c;
@@ -655,23 +698,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags)
/*
* Read the next char, but don't move the reading pointer.
*/
-static int cfg_peek(diskfile_backend *cfg, int flags)
+static int reader_peek(struct reader *reader, int flags)
{
void *old_read_ptr;
int old_lineno, old_eof;
int ret;
- assert(cfg->reader.read_ptr);
+ assert(reader->read_ptr);
- old_read_ptr = cfg->reader.read_ptr;
- old_lineno = cfg->reader.line_number;
- old_eof = cfg->reader.eof;
+ old_read_ptr = reader->read_ptr;
+ old_lineno = reader->line_number;
+ old_eof = reader->eof;
- ret = cfg_getchar(cfg, flags);
+ ret = reader_getchar(reader, flags);
- cfg->reader.read_ptr = old_read_ptr;
- cfg->reader.line_number = old_lineno;
- cfg->reader.eof = old_eof;
+ reader->read_ptr = old_read_ptr;
+ reader->line_number = old_lineno;
+ reader->eof = old_eof;
return ret;
}
@@ -679,13 +722,13 @@ static int cfg_peek(diskfile_backend *cfg, int flags)
/*
* Read and consume a line, returning it in newly-allocated memory.
*/
-static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
+static char *reader_readline(struct reader *reader, bool skip_whitespace)
{
char *line = NULL;
char *line_src, *line_end;
size_t line_len;
- line_src = cfg->reader.read_ptr;
+ line_src = reader->read_ptr;
if (skip_whitespace) {
/* Skip empty empty lines */
@@ -714,10 +757,10 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
line_end++;
if (*line_end == '\0')
- cfg->reader.eof = 1;
+ reader->eof = 1;
- cfg->reader.line_number++;
- cfg->reader.read_ptr = line_end;
+ reader->line_number++;
+ reader->read_ptr = line_end;
return line;
}
@@ -725,11 +768,11 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
/*
* Consume a line, without storing it anywhere
*/
-static void cfg_consume_line(diskfile_backend *cfg)
+static void reader_consume_line(struct reader *reader)
{
char *line_start, *line_end;
- line_start = cfg->reader.read_ptr;
+ line_start = reader->read_ptr;
line_end = strchr(line_start, '\n');
/* No newline at EOF */
if(line_end == NULL){
@@ -740,10 +783,10 @@ static void cfg_consume_line(diskfile_backend *cfg)
line_end++;
if (*line_end == '\0')
- cfg->reader.eof = 1;
+ reader->eof = 1;
- cfg->reader.line_number++;
- cfg->reader.read_ptr = line_end;
+ reader->line_number++;
+ reader->read_ptr = line_end;
}
GIT_INLINE(int) config_keychar(int c)
@@ -751,12 +794,11 @@ GIT_INLINE(int) config_keychar(int c)
return isalnum(c) || c == '-';
}
-static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name)
+static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
{
int c, rpos;
char *first_quote, *last_quote;
git_buf buf = GIT_BUF_INIT;
- int quote_marks;
/*
* base_name is what came before the space. We should be at the
* first quotation mark, except for now, line isn't being kept in
@@ -767,7 +809,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
last_quote = strrchr(line, '"');
if (last_quote - first_quote == 0) {
- set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
+ set_parse_error(reader, 0, "Missing closing quotation mark in section header");
return -1;
}
@@ -775,37 +817,30 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
git_buf_printf(&buf, "%s.", base_name);
rpos = 0;
- quote_marks = 0;
line = first_quote;
- c = line[rpos++];
+ c = line[++rpos];
/*
* At the end of each iteration, whatever is stored in c will be
* added to the string. In case of error, jump to out
*/
do {
- if (quote_marks == 2) {
- set_parse_error(cfg, rpos, "Unexpected text after closing quotes");
+
+ switch (c) {
+ case 0:
+ set_parse_error(reader, 0, "Unexpected end-of-line in section header");
git_buf_free(&buf);
return -1;
- }
- switch (c) {
case '"':
- ++quote_marks;
- continue;
+ goto end_parse;
case '\\':
- c = line[rpos++];
-
- switch (c) {
- case '"':
- case '\\':
- break;
+ c = line[++rpos];
- default:
- set_parse_error(cfg, rpos, "Unsupported escape sequence");
+ if (c == 0) {
+ set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
git_buf_free(&buf);
return -1;
}
@@ -814,29 +849,37 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
break;
}
- git_buf_putc(&buf, c);
- } while ((c = line[rpos++]) != ']');
+ git_buf_putc(&buf, (char)c);
+ c = line[++rpos];
+ } while (line + rpos < last_quote);
+
+end_parse:
+ if (line[rpos] != '"' || line[rpos + 1] != ']') {
+ set_parse_error(reader, rpos, "Unexpected text after closing quotes");
+ git_buf_free(&buf);
+ return -1;
+ }
*section_name = git_buf_detach(&buf);
return 0;
}
-static int parse_section_header(diskfile_backend *cfg, char **section_out)
+static int parse_section_header(struct reader *reader, char **section_out)
{
char *name, *name_end;
int name_length, c, pos;
int result;
char *line;
- line = cfg_readline(cfg, true);
+ line = reader_readline(reader, true);
if (line == NULL)
return -1;
/* find the end of the variable's name */
- name_end = strchr(line, ']');
+ name_end = strrchr(line, ']');
if (name_end == NULL) {
git__free(line);
- set_parse_error(cfg, 0, "Missing ']' in section header");
+ set_parse_error(reader, 0, "Missing ']' in section header");
return -1;
}
@@ -855,14 +898,14 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
do {
if (git__isspace(c)){
name[name_length] = '\0';
- result = parse_section_header_ext(cfg, line, name, section_out);
+ result = parse_section_header_ext(reader, line, name, section_out);
git__free(line);
git__free(name);
return result;
}
if (!config_keychar(c) && c != '.') {
- set_parse_error(cfg, pos, "Unexpected character in header");
+ set_parse_error(reader, pos, "Unexpected character in header");
goto fail_parse;
}
@@ -871,7 +914,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
} while ((c = line[pos++]) != ']');
if (line[pos - 1] != ']') {
- set_parse_error(cfg, pos, "Unexpected end of file");
+ set_parse_error(reader, pos, "Unexpected end of file");
goto fail_parse;
}
@@ -888,14 +931,14 @@ fail_parse:
return -1;
}
-static int skip_bom(diskfile_backend *cfg)
+static int skip_bom(struct reader *reader)
{
git_bom_t bom;
int bom_offset = git_buf_text_detect_bom(&bom,
- &cfg->reader.buffer, cfg->reader.read_ptr - cfg->reader.buffer.ptr);
+ &reader->buffer, reader->read_ptr - reader->buffer.ptr);
if (bom == GIT_BOM_UTF8)
- cfg->reader.read_ptr += bom_offset;
+ reader->read_ptr += bom_offset;
/* TODO: reference implementation is pretty stupid with BoM */
@@ -965,7 +1008,16 @@ static int strip_comments(char *line, int in_quotes)
return quote_count;
}
-static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
+static int included_path(git_buf *out, const char *dir, const char *path)
+{
+ /* From the user's home */
+ if (path[0] == '~' && path[1] == '/')
+ return git_futils_find_global_file(out, &path[1]);
+
+ return git_path_join_unrooted(out, path, dir, NULL);
+}
+
+static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
{
int c;
char *current_section = NULL;
@@ -975,39 +1027,46 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
git_buf buf = GIT_BUF_INIT;
int result = 0;
khiter_t pos;
+ uint32_t reader_idx;
+
+ if (depth >= MAX_INCLUDE_DEPTH) {
+ giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
+ return -1;
+ }
+ reader_idx = git_array_size(cfg_file->readers) - 1;
/* Initialize the reading position */
- cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr;
- cfg_file->reader.eof = 0;
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
/* If the file is empty, there's nothing for us to do */
- if (*cfg_file->reader.read_ptr == '\0')
+ if (*reader->read_ptr == '\0')
return 0;
- skip_bom(cfg_file);
+ skip_bom(reader);
- while (result == 0 && !cfg_file->reader.eof) {
+ while (result == 0 && !reader->eof) {
- c = cfg_peek(cfg_file, SKIP_WHITESPACE);
+ c = reader_peek(reader, SKIP_WHITESPACE);
switch (c) {
case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
- cfg_file->reader.eof = 1;
+ reader->eof = 1;
break;
case '[': /* section header, new section begins */
git__free(current_section);
current_section = NULL;
- result = parse_section_header(cfg_file, &current_section);
+ result = parse_section_header(reader, &current_section);
break;
case ';':
case '#':
- cfg_consume_line(cfg_file);
+ reader_consume_line(reader);
break;
default: /* assume variable declaration */
- result = parse_variable(cfg_file, &var_name, &var_value);
+ result = parse_variable(reader, &var_name, &var_value);
if (result < 0)
break;
@@ -1028,6 +1087,7 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
var->entry->name = git_buf_detach(&buf);
var->entry->value = var_value;
var->entry->level = level;
+ var->included = !!depth;
/* Add or append the new config option */
pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
@@ -1044,6 +1104,42 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
existing->next = var;
}
+ if (!git__strcmp(var->entry->name, "include.path")) {
+ struct reader *r;
+ git_buf path = GIT_BUF_INIT;
+ char *dir;
+ uint32_t index;
+
+ r = git_array_alloc(cfg_file->readers);
+ /* The reader may have been reallocated */
+ reader = git_array_get(cfg_file->readers, reader_idx);
+ memset(r, 0, sizeof(struct reader));
+ if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
+ break;
+
+ /* We need to know out index in the array, as the next config_parse call may realloc */
+ index = git_array_size(cfg_file->readers) - 1;
+ dir = git_buf_detach(&path);
+ result = included_path(&path, dir, var->entry->value);
+ git__free(dir);
+
+ if (result < 0)
+ break;
+
+ r->file_path = git_buf_detach(&path);
+ git_buf_init(&r->buffer, 0);
+ if ((result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime,
+ &r->file_size, NULL)) < 0)
+ break;
+
+ result = config_parse(cfg_file, r, level, depth+1);
+ r = git_array_get(cfg_file->readers, index);
+ git_buf_free(&r->buffer);
+
+ if (result < 0)
+ break;
+ }
+
break;
}
}
@@ -1082,6 +1178,24 @@ static int write_section(git_filebuf *file, const char *key)
return result;
}
+static const char *quotes_for_value(const char *value)
+{
+ const char *ptr;
+
+ if (value[0] == ' ' || value[0] == '\0')
+ return "\"";
+
+ for (ptr = value; *ptr; ++ptr) {
+ if (*ptr == ';' || *ptr == '#')
+ return "\"";
+ }
+
+ if (ptr[-1] == ' ')
+ return "\"";
+
+ return "";
+}
+
/*
* This is pretty much the parsing, except we write out anything we don't have
*/
@@ -1089,38 +1203,44 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
{
int result, c;
int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0;
- const char *pre_end = NULL, *post_start = NULL, *data_start;
+ const char *pre_end = NULL, *post_start = NULL, *data_start, *write_start;
char *current_section = NULL, *section, *name, *ldot;
git_filebuf file = GIT_FILEBUF_INIT;
+ struct reader *reader = git_array_get(cfg->readers, 0);
/* We need to read in our own config file */
- result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
+ result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
/* Initialise the reading position */
if (result == GIT_ENOTFOUND) {
- cfg->reader.read_ptr = NULL;
- cfg->reader.eof = 1;
+ reader->read_ptr = NULL;
+ reader->eof = 1;
data_start = NULL;
- git_buf_clear(&cfg->reader.buffer);
+ git_buf_clear(&reader->buffer);
} else if (result == 0) {
- cfg->reader.read_ptr = cfg->reader.buffer.ptr;
- cfg->reader.eof = 0;
- data_start = cfg->reader.read_ptr;
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
+ data_start = reader->read_ptr;
} else {
return -1; /* OS error when reading the file */
}
+ write_start = data_start;
+
/* Lock the file */
- if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
- return -1;
+ if ((result = git_filebuf_open(
+ &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) {
+ git_buf_free(&reader->buffer);
+ return result;
+ }
- skip_bom(cfg);
+ skip_bom(reader);
ldot = strrchr(key, '.');
name = ldot + 1;
section = git__strndup(key, ldot - key);
- while (!cfg->reader.eof) {
- c = cfg_peek(cfg, SKIP_WHITESPACE);
+ while (!reader->eof) {
+ c = reader_peek(reader, SKIP_WHITESPACE);
if (c == '\0') { /* We've arrived at the end of the file */
break;
@@ -1133,11 +1253,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
* new section. If we actually want to replace it, the
* default case will take care of updating them.
*/
- pre_end = post_start = cfg->reader.read_ptr;
+ pre_end = post_start = reader->read_ptr;
git__free(current_section);
current_section = NULL;
- if (parse_section_header(cfg, &current_section) < 0)
+ if (parse_section_header(reader, &current_section) < 0)
goto rewrite_fail;
/* Keep track of when it stops matching */
@@ -1146,7 +1266,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
}
else if (c == ';' || c == '#') {
- cfg_consume_line(cfg);
+ reader_consume_line(reader);
}
else {
@@ -1162,15 +1282,15 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
*/
if (!section_matches) {
if (!last_section_matched) {
- cfg_consume_line(cfg);
+ reader_consume_line(reader);
continue;
}
} else {
int has_matched = 0;
char *var_name, *var_value;
- pre_end = cfg->reader.read_ptr;
- if (parse_variable(cfg, &var_name, &var_value) < 0)
+ pre_end = reader->read_ptr;
+ if (parse_variable(reader, &var_name, &var_value) < 0)
goto rewrite_fail;
/* First try to match the name of the variable */
@@ -1189,23 +1309,28 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
if (!has_matched)
continue;
- post_start = cfg->reader.read_ptr;
+ post_start = reader->read_ptr;
}
/* We've found the variable we wanted to change, so
* write anything up to it */
- git_filebuf_write(&file, data_start, pre_end - data_start);
+ git_filebuf_write(&file, write_start, pre_end - write_start);
preg_replaced = 1;
/* Then replace the variable. If the value is NULL, it
* means we want to delete it, so don't write anything. */
if (value != NULL) {
- git_filebuf_printf(&file, "\t%s = %s\n", name, value);
+ const char *q = quotes_for_value(value);
+ git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
}
- /* multiline variable? we need to keep reading lines to match */
- if (preg != NULL) {
- data_start = post_start;
+ /*
+ * If we have a multivar, we should keep looking for entries,
+ * but only if we're in the right section. Otherwise we'll end up
+ * looping on the edge of a matching and a non-matching section.
+ */
+ if (section_matches && preg != NULL) {
+ write_start = post_start;
continue;
}
@@ -1232,12 +1357,14 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
*/
if (write_trailer) {
/* Write out rest of the file */
- git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start));
+ git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
} else {
if (preg_replaced) {
- git_filebuf_printf(&file, "\n%s", data_start);
+ git_filebuf_printf(&file, "\n%s", write_start);
} else {
- git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size);
+ const char *q;
+
+ git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
/* And now if we just need to add a variable */
if (!section_matches && write_section(&file, section) < 0)
@@ -1253,10 +1380,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
}
/* If we are here, there is at least a section line */
- if (cfg->reader.buffer.size > 0 && *(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n')
+ if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
git_filebuf_write(&file, "\n", 1);
- git_filebuf_printf(&file, "\t%s = %s\n", name, value);
+ q = quotes_for_value(value);
+ git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
}
}
@@ -1264,10 +1392,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git__free(current_section);
/* refresh stats - if this errors, then commit will error too */
- (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);
+ (void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file);
- result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
- git_buf_free(&cfg->reader.buffer);
+ result = git_filebuf_commit(&file);
+ git_buf_free(&reader->buffer);
return result;
@@ -1276,7 +1404,7 @@ rewrite_fail:
git__free(current_section);
git_filebuf_cleanup(&file);
- git_buf_free(&cfg->reader.buffer);
+ git_buf_free(&reader->buffer);
return -1;
}
@@ -1293,6 +1421,9 @@ static char *escape_value(const char *ptr)
assert(ptr);
len = strlen(ptr);
+ if (!len)
+ return git__calloc(1, sizeof(char));
+
git_buf_grow(&buf, len);
while (*ptr != '\0') {
@@ -1365,19 +1496,19 @@ static int is_multiline_var(const char *str)
return (end > str) && (count & 1);
}
-static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes)
+static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
{
char *line = NULL, *proc_line = NULL;
int quote_count;
/* Check that the next line exists */
- line = cfg_readline(cfg, false);
+ line = reader_readline(reader, false);
if (line == NULL)
return -1;
/* We've reached the end of the file, there is input missing */
if (line[0] == '\0') {
- set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var");
+ set_parse_error(reader, 0, "Unexpected end of file while parsing multine var");
git__free(line);
return -1;
}
@@ -1387,7 +1518,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
/* If it was just a comment, pretend it didn't exist */
if (line[0] == '\0') {
git__free(line);
- return parse_multiline_variable(cfg, value, quote_count);
+ return parse_multiline_variable(reader, value, quote_count);
/* TODO: unbounded recursion. This **could** be exploitable */
}
@@ -1395,7 +1526,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
* standard, this character **has** to be last one in the buf, with
* no whitespace after it */
assert(is_multiline_var(value->ptr));
- git_buf_truncate(value, git_buf_len(value) - 1);
+ git_buf_shorten(value, 1);
proc_line = fixup_line(line, in_quotes);
if (proc_line == NULL) {
@@ -1412,19 +1543,19 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
* keep putting stuff in the buffer
*/
if (is_multiline_var(value->ptr))
- return parse_multiline_variable(cfg, value, quote_count);
+ return parse_multiline_variable(reader, value, quote_count);
return 0;
}
-static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
+static int parse_variable(struct reader *reader, char **var_name, char **var_value)
{
const char *var_end = NULL;
const char *value_start = NULL;
char *line;
int quote_count;
- line = cfg_readline(cfg, true);
+ line = reader_readline(reader, true);
if (line == NULL)
return -1;
@@ -1459,7 +1590,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
GITERR_CHECK_ALLOC(proc_line);
git_buf_puts(&multi_value, proc_line);
git__free(proc_line);
- if (parse_multiline_variable(cfg, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
+ if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
git__free(*var_name);
git__free(line);
git_buf_free(&multi_value);