Commit d209cc47 by Carlos Martín Nieto

config: decouple the backend from the reader at the low level

In order to support config includes, we must differentiate between the
backend's main file and the file we are currently parsing.

This lays the groundwork for includes, keeping the current behaviours.
parent cca9bea4
......@@ -25,6 +25,7 @@ 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 {
......@@ -72,17 +73,20 @@ typedef struct git_config_file_iter {
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
(iter) = (tmp))
struct reader {
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;
struct reader reader;
char *file_path;
time_t file_mtime;
......@@ -91,15 +95,15 @@ typedef struct {
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)
......@@ -171,7 +175,7 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
if (res == GIT_ENOTFOUND)
return 0;
if (res < 0 || (res = config_parse(b, level)) < 0) {
if (res < 0 || (res = config_parse(b, &b->reader, level, 0)) < 0) {
free_vars(b->values);
b->values = NULL;
}
......@@ -196,7 +200,7 @@ 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, &b->reader, b->level, 0)) < 0) {
free_vars(b->values);
b->values = old_values;
} else {
......@@ -214,6 +218,7 @@ static void backend_free(git_config_backend *_backend)
if (backend == NULL)
return;
git__free(backend->reader.file_path);
git__free(backend->file_path);
free_vars(backend->values);
git__free(backend);
......@@ -501,6 +506,10 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
backend->file_path = git__strdup(path);
GITERR_CHECK_ALLOC(backend->file_path);
/* the reader needs its own copy */
backend->reader.file_path = git__strdup(path);
GITERR_CHECK_ALLOC(backend->reader.file_path);
backend->parent.open = config_open;
backend->parent.get = config_get;
backend->parent.set = config_set;
......@@ -515,26 +524,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';
}
......@@ -544,21 +553,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;
......@@ -567,23 +578,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;
}
......@@ -591,13 +602,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 */
......@@ -626,10 +637,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;
}
......@@ -637,11 +648,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){
......@@ -652,10 +663,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)
......@@ -663,7 +674,7 @@ 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;
......@@ -679,7 +690,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;
}
......@@ -698,14 +709,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
*/
do {
if (quote_marks == 2) {
set_parse_error(cfg, rpos, "Unexpected text after closing quotes");
set_parse_error(reader, rpos, "Unexpected text after closing quotes");
git_buf_free(&buf);
return -1;
}
switch (c) {
case 0:
set_parse_error(cfg, 0, "Unexpected end-of-line in section header");
set_parse_error(reader, 0, "Unexpected end-of-line in section header");
git_buf_free(&buf);
return -1;
......@@ -719,7 +730,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
switch (c) {
case '"':
if (&line[rpos-1] == last_quote) {
set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
set_parse_error(reader, 0, "Missing closing quotation mark in section header");
git_buf_free(&buf);
return -1;
}
......@@ -728,7 +739,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
break;
default:
set_parse_error(cfg, rpos, "Unsupported escape sequence");
set_parse_error(reader, rpos, "Unsupported escape sequence");
git_buf_free(&buf);
return -1;
}
......@@ -744,14 +755,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
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;
......@@ -759,7 +770,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
name_end = strchr(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;
}
......@@ -778,14 +789,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;
}
......@@ -794,7 +805,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;
}
......@@ -811,14 +822,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 */
......@@ -888,7 +899,7 @@ 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 config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
{
int c;
char *current_section = NULL;
......@@ -900,37 +911,37 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
khiter_t pos;
/* 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;
......@@ -951,6 +962,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);
......@@ -1037,13 +1049,13 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
return -1;
skip_bom(cfg);
skip_bom(&cfg->reader);
ldot = strrchr(key, '.');
name = ldot + 1;
section = git__strndup(key, ldot - key);
while (!cfg->reader.eof) {
c = cfg_peek(cfg, SKIP_WHITESPACE);
c = reader_peek(&cfg->reader, SKIP_WHITESPACE);
if (c == '\0') { /* We've arrived at the end of the file */
break;
......@@ -1060,7 +1072,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git__free(current_section);
current_section = NULL;
if (parse_section_header(cfg, &current_section) < 0)
if (parse_section_header(&cfg->reader, &current_section) < 0)
goto rewrite_fail;
/* Keep track of when it stops matching */
......@@ -1069,7 +1081,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(&cfg->reader);
}
else {
......@@ -1085,7 +1097,7 @@ 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(&cfg->reader);
continue;
}
} else {
......@@ -1093,7 +1105,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
char *var_name, *var_value;
pre_end = cfg->reader.read_ptr;
if (parse_variable(cfg, &var_name, &var_value) < 0)
if (parse_variable(&cfg->reader, &var_name, &var_value) < 0)
goto rewrite_fail;
/* First try to match the name of the variable */
......@@ -1291,19 +1303,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;
}
......@@ -1313,7 +1325,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 */
}
......@@ -1338,19 +1350,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;
......@@ -1385,7 +1397,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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment