Commit 4581f9d8 by Vicent Martí

Merge pull request #1833 from libgit2/cmn/config-include

Support config includes
parents 92457cad 53ea0513
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "git2/sys/config.h" #include "git2/sys/config.h"
#include "git2/types.h" #include "git2/types.h"
#include "strmap.h" #include "strmap.h"
#include "array.h"
#include <ctype.h> #include <ctype.h>
#include <sys/types.h> #include <sys/types.h>
...@@ -25,6 +26,7 @@ GIT__USE_STRMAP; ...@@ -25,6 +26,7 @@ GIT__USE_STRMAP;
typedef struct cvar_t { typedef struct cvar_t {
struct cvar_t *next; struct cvar_t *next;
git_config_entry *entry; git_config_entry *entry;
int included; /* whether this is part of [include] */
} cvar_t; } cvar_t;
typedef struct git_config_file_iter { typedef struct git_config_file_iter {
...@@ -33,6 +35,8 @@ typedef struct git_config_file_iter { ...@@ -33,6 +35,8 @@ typedef struct git_config_file_iter {
cvar_t* next_var; cvar_t* next_var;
} git_config_file_iter; } git_config_file_iter;
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
#define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_HEAD(list) ((list)->head)
...@@ -72,34 +76,37 @@ typedef struct git_config_file_iter { ...@@ -72,34 +76,37 @@ typedef struct git_config_file_iter {
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
(iter) = (tmp)) (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 { typedef struct {
git_config_backend parent; git_config_backend parent;
git_strmap *values; git_strmap *values;
struct { git_array_t(struct reader) readers;
git_buf buffer;
char *read_ptr;
int line_number;
int eof;
} reader;
char *file_path; char *file_path;
time_t file_mtime;
size_t file_size;
git_config_level_t level; git_config_level_t level;
} diskfile_backend; } diskfile_backend;
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);
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);
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *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 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)", 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) static void cvar_free(cvar_t *var)
...@@ -156,6 +163,7 @@ static void free_vars(git_strmap *values) ...@@ -156,6 +163,7 @@ static void free_vars(git_strmap *values)
static int config_open(git_config_backend *cfg, git_config_level_t level) static int config_open(git_config_backend *cfg, git_config_level_t level)
{ {
int res; int res;
struct reader *reader;
diskfile_backend *b = (diskfile_backend *)cfg; diskfile_backend *b = (diskfile_backend *)cfg;
b->level = level; b->level = level;
...@@ -163,32 +171,52 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) ...@@ -163,32 +171,52 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
b->values = git_strmap_alloc(); b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values); 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( 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 */ /* It's fine if the file doesn't exist */
if (res == GIT_ENOTFOUND) if (res == GIT_ENOTFOUND)
return 0; 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); free_vars(b->values);
b->values = NULL; b->values = NULL;
} }
git_buf_free(&b->reader.buffer); reader = git_array_get(b->readers, 0);
git_buf_free(&reader->buffer);
return res; return res;
} }
static int config_refresh(git_config_backend *cfg) 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; diskfile_backend *b = (diskfile_backend *)cfg;
git_strmap *old_values; git_strmap *old_values;
struct reader *reader;
uint32_t i;
for (i = 0; i < git_array_size(b->readers); i++) {
reader = git_array_get(b->readers, i);
res = git_futils_readbuffer_updated( res = git_futils_readbuffer_updated(
&b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated); &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated);
if (res < 0 || !updated)
if (res < 0)
return (res == GIT_ENOTFOUND) ? 0 : res;
if (updated)
any_updated = 1;
}
if (!any_updated)
return (res == GIT_ENOTFOUND) ? 0 : res; return (res == GIT_ENOTFOUND) ? 0 : res;
/* need to reload - store old values and prep for reload */ /* need to reload - store old values and prep for reload */
...@@ -196,24 +224,31 @@ static int config_refresh(git_config_backend *cfg) ...@@ -196,24 +224,31 @@ static int config_refresh(git_config_backend *cfg)
b->values = git_strmap_alloc(); b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values); 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); free_vars(b->values);
b->values = old_values; b->values = old_values;
} else { } else {
free_vars(old_values); free_vars(old_values);
} }
git_buf_free(&b->reader.buffer); git_buf_free(&reader->buffer);
return res; return res;
} }
static void backend_free(git_config_backend *_backend) static void backend_free(git_config_backend *_backend)
{ {
diskfile_backend *backend = (diskfile_backend *)_backend; diskfile_backend *backend = (diskfile_backend *)_backend;
uint32_t i;
if (backend == NULL) if (backend == NULL)
return; 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); git__free(backend->file_path);
free_vars(backend->values); free_vars(backend->values);
git__free(backend); git__free(backend);
...@@ -361,6 +396,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git ...@@ -361,6 +396,7 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
char *key; char *key;
khiter_t pos; khiter_t pos;
int error; int error;
cvar_t *var;
if ((error = git_config__normalize_name(name, &key)) < 0) if ((error = git_config__normalize_name(name, &key)) < 0)
return error; return error;
...@@ -372,7 +408,11 @@ static int config_get(const git_config_backend *cfg, const char *name, const git ...@@ -372,7 +408,11 @@ static int config_get(const git_config_backend *cfg, const char *name, const git
if (!git_strmap_valid_index(b->values, pos)) if (!git_strmap_valid_index(b->values, pos))
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
*out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry; var = git_strmap_value_at(b->values, pos);
while (var->next)
var = var->next;
*out = var->entry;
return 0; return 0;
} }
...@@ -515,26 +555,26 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) ...@@ -515,26 +555,26 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
return 0; return 0;
} }
static int cfg_getchar_raw(diskfile_backend *cfg) static int reader_getchar_raw(struct reader *reader)
{ {
int c; int c;
c = *cfg->reader.read_ptr++; c = *reader->read_ptr++;
/* /*
Win 32 line breaks: if we find a \r\n sequence, Win 32 line breaks: if we find a \r\n sequence,
return only the \n as a newline return only the \n as a newline
*/ */
if (c == '\r' && *cfg->reader.read_ptr == '\n') { if (c == '\r' && *reader->read_ptr == '\n') {
cfg->reader.read_ptr++; reader->read_ptr++;
c = '\n'; c = '\n';
} }
if (c == '\n') if (c == '\n')
cfg->reader.line_number++; reader->line_number++;
if (c == 0) { if (c == 0) {
cfg->reader.eof = 1; reader->eof = 1;
c = '\n'; c = '\n';
} }
...@@ -544,21 +584,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg) ...@@ -544,21 +584,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg)
#define SKIP_WHITESPACE (1 << 1) #define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2) #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_whitespace = (flags & SKIP_WHITESPACE);
const int skip_comments = (flags & SKIP_COMMENTS); const int skip_comments = (flags & SKIP_COMMENTS);
int c; int c;
assert(cfg_file->reader.read_ptr); assert(reader->read_ptr);
do c = cfg_getchar_raw(cfg_file); do {
while (skip_whitespace && git__isspace(c) && c = reader_getchar_raw(reader);
!cfg_file->reader.eof); } while (skip_whitespace && git__isspace(c) &&
!reader->eof);
if (skip_comments && (c == '#' || c == ';')) { if (skip_comments && (c == '#' || c == ';')) {
do c = cfg_getchar_raw(cfg_file); do {
while (c != '\n'); c = reader_getchar_raw(reader);
} while (c != '\n');
} }
return c; return c;
...@@ -567,23 +609,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags) ...@@ -567,23 +609,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags)
/* /*
* Read the next char, but don't move the reading pointer. * 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; void *old_read_ptr;
int old_lineno, old_eof; int old_lineno, old_eof;
int ret; int ret;
assert(cfg->reader.read_ptr); assert(reader->read_ptr);
old_read_ptr = cfg->reader.read_ptr; old_read_ptr = reader->read_ptr;
old_lineno = cfg->reader.line_number; old_lineno = reader->line_number;
old_eof = cfg->reader.eof; old_eof = reader->eof;
ret = cfg_getchar(cfg, flags); ret = reader_getchar(reader, flags);
cfg->reader.read_ptr = old_read_ptr; reader->read_ptr = old_read_ptr;
cfg->reader.line_number = old_lineno; reader->line_number = old_lineno;
cfg->reader.eof = old_eof; reader->eof = old_eof;
return ret; return ret;
} }
...@@ -591,13 +633,13 @@ static int cfg_peek(diskfile_backend *cfg, int flags) ...@@ -591,13 +633,13 @@ static int cfg_peek(diskfile_backend *cfg, int flags)
/* /*
* Read and consume a line, returning it in newly-allocated memory. * 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 = NULL;
char *line_src, *line_end; char *line_src, *line_end;
size_t line_len; size_t line_len;
line_src = cfg->reader.read_ptr; line_src = reader->read_ptr;
if (skip_whitespace) { if (skip_whitespace) {
/* Skip empty empty lines */ /* Skip empty empty lines */
...@@ -626,10 +668,10 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) ...@@ -626,10 +668,10 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
line_end++; line_end++;
if (*line_end == '\0') if (*line_end == '\0')
cfg->reader.eof = 1; reader->eof = 1;
cfg->reader.line_number++; reader->line_number++;
cfg->reader.read_ptr = line_end; reader->read_ptr = line_end;
return line; return line;
} }
...@@ -637,11 +679,11 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) ...@@ -637,11 +679,11 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
/* /*
* Consume a line, without storing it anywhere * 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; char *line_start, *line_end;
line_start = cfg->reader.read_ptr; line_start = reader->read_ptr;
line_end = strchr(line_start, '\n'); line_end = strchr(line_start, '\n');
/* No newline at EOF */ /* No newline at EOF */
if(line_end == NULL){ if(line_end == NULL){
...@@ -652,10 +694,10 @@ static void cfg_consume_line(diskfile_backend *cfg) ...@@ -652,10 +694,10 @@ static void cfg_consume_line(diskfile_backend *cfg)
line_end++; line_end++;
if (*line_end == '\0') if (*line_end == '\0')
cfg->reader.eof = 1; reader->eof = 1;
cfg->reader.line_number++; reader->line_number++;
cfg->reader.read_ptr = line_end; reader->read_ptr = line_end;
} }
GIT_INLINE(int) config_keychar(int c) GIT_INLINE(int) config_keychar(int c)
...@@ -663,7 +705,7 @@ GIT_INLINE(int) config_keychar(int c) ...@@ -663,7 +705,7 @@ GIT_INLINE(int) config_keychar(int c)
return isalnum(c) || 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; int c, rpos;
char *first_quote, *last_quote; char *first_quote, *last_quote;
...@@ -679,7 +721,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con ...@@ -679,7 +721,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
last_quote = strrchr(line, '"'); last_quote = strrchr(line, '"');
if (last_quote - first_quote == 0) { 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; return -1;
} }
...@@ -698,14 +740,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con ...@@ -698,14 +740,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
*/ */
do { do {
if (quote_marks == 2) { 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); git_buf_free(&buf);
return -1; return -1;
} }
switch (c) { switch (c) {
case 0: 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); git_buf_free(&buf);
return -1; return -1;
...@@ -719,7 +761,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con ...@@ -719,7 +761,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
switch (c) { switch (c) {
case '"': case '"':
if (&line[rpos-1] == last_quote) { 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); git_buf_free(&buf);
return -1; return -1;
} }
...@@ -728,7 +770,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con ...@@ -728,7 +770,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
break; break;
default: default:
set_parse_error(cfg, rpos, "Unsupported escape sequence"); set_parse_error(reader, rpos, "Unsupported escape sequence");
git_buf_free(&buf); git_buf_free(&buf);
return -1; return -1;
} }
...@@ -744,14 +786,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con ...@@ -744,14 +786,14 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
return 0; 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; char *name, *name_end;
int name_length, c, pos; int name_length, c, pos;
int result; int result;
char *line; char *line;
line = cfg_readline(cfg, true); line = reader_readline(reader, true);
if (line == NULL) if (line == NULL)
return -1; return -1;
...@@ -759,7 +801,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) ...@@ -759,7 +801,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
name_end = strchr(line, ']'); name_end = strchr(line, ']');
if (name_end == NULL) { if (name_end == NULL) {
git__free(line); git__free(line);
set_parse_error(cfg, 0, "Missing ']' in section header"); set_parse_error(reader, 0, "Missing ']' in section header");
return -1; return -1;
} }
...@@ -778,14 +820,14 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) ...@@ -778,14 +820,14 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
do { do {
if (git__isspace(c)){ if (git__isspace(c)){
name[name_length] = '\0'; 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(line);
git__free(name); git__free(name);
return result; return result;
} }
if (!config_keychar(c) && c != '.') { 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; goto fail_parse;
} }
...@@ -794,7 +836,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) ...@@ -794,7 +836,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
} while ((c = line[pos++]) != ']'); } while ((c = line[pos++]) != ']');
if (line[pos - 1] != ']') { 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; goto fail_parse;
} }
...@@ -811,14 +853,14 @@ fail_parse: ...@@ -811,14 +853,14 @@ fail_parse:
return -1; return -1;
} }
static int skip_bom(diskfile_backend *cfg) static int skip_bom(struct reader *reader)
{ {
git_bom_t bom; git_bom_t bom;
int bom_offset = git_buf_text_detect_bom(&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) if (bom == GIT_BOM_UTF8)
cfg->reader.read_ptr += bom_offset; reader->read_ptr += bom_offset;
/* TODO: reference implementation is pretty stupid with BoM */ /* TODO: reference implementation is pretty stupid with BoM */
...@@ -888,7 +930,16 @@ static int strip_comments(char *line, int in_quotes) ...@@ -888,7 +930,16 @@ static int strip_comments(char *line, int in_quotes)
return quote_count; 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; int c;
char *current_section = NULL; char *current_section = NULL;
...@@ -898,39 +949,46 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) ...@@ -898,39 +949,46 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
int result = 0; int result = 0;
khiter_t pos; 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 */ /* Initialize the reading position */
cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; reader->read_ptr = reader->buffer.ptr;
cfg_file->reader.eof = 0; reader->eof = 0;
/* If the file is empty, there's nothing for us to do */ /* 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; 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) { switch (c) {
case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */ case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
cfg_file->reader.eof = 1; reader->eof = 1;
break; break;
case '[': /* section header, new section begins */ case '[': /* section header, new section begins */
git__free(current_section); git__free(current_section);
current_section = NULL; current_section = NULL;
result = parse_section_header(cfg_file, &current_section); result = parse_section_header(reader, &current_section);
break; break;
case ';': case ';':
case '#': case '#':
cfg_consume_line(cfg_file); reader_consume_line(reader);
break; break;
default: /* assume variable declaration */ default: /* assume variable declaration */
result = parse_variable(cfg_file, &var_name, &var_value); result = parse_variable(reader, &var_name, &var_value);
if (result < 0) if (result < 0)
break; break;
...@@ -951,6 +1009,7 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) ...@@ -951,6 +1009,7 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
var->entry->name = git_buf_detach(&buf); var->entry->name = git_buf_detach(&buf);
var->entry->value = var_value; var->entry->value = var_value;
var->entry->level = level; var->entry->level = level;
var->included = !!depth;
/* Add or append the new config option */ /* Add or append the new config option */
pos = git_strmap_lookup_index(cfg_file->values, var->entry->name); pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
...@@ -967,6 +1026,42 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) ...@@ -967,6 +1026,42 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
existing->next = var; 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; break;
} }
} }
...@@ -1015,20 +1110,21 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1015,20 +1110,21 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
const char *pre_end = NULL, *post_start = NULL, *data_start; const char *pre_end = NULL, *post_start = NULL, *data_start;
char *current_section = NULL, *section, *name, *ldot; char *current_section = NULL, *section, *name, *ldot;
git_filebuf file = GIT_FILEBUF_INIT; git_filebuf file = GIT_FILEBUF_INIT;
struct reader *reader = git_array_get(cfg->readers, 0);
/* We need to read in our own config file */ /* 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 */ /* Initialise the reading position */
if (result == GIT_ENOTFOUND) { if (result == GIT_ENOTFOUND) {
cfg->reader.read_ptr = NULL; reader->read_ptr = NULL;
cfg->reader.eof = 1; reader->eof = 1;
data_start = NULL; data_start = NULL;
git_buf_clear(&cfg->reader.buffer); git_buf_clear(&reader->buffer);
} else if (result == 0) { } else if (result == 0) {
cfg->reader.read_ptr = cfg->reader.buffer.ptr; reader->read_ptr = reader->buffer.ptr;
cfg->reader.eof = 0; reader->eof = 0;
data_start = cfg->reader.read_ptr; data_start = reader->read_ptr;
} else { } else {
return -1; /* OS error when reading the file */ return -1; /* OS error when reading the file */
} }
...@@ -1037,13 +1133,13 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1037,13 +1133,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) if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
return -1; return -1;
skip_bom(cfg); skip_bom(reader);
ldot = strrchr(key, '.'); ldot = strrchr(key, '.');
name = ldot + 1; name = ldot + 1;
section = git__strndup(key, ldot - key); section = git__strndup(key, ldot - key);
while (!cfg->reader.eof) { while (!reader->eof) {
c = cfg_peek(cfg, SKIP_WHITESPACE); c = reader_peek(reader, SKIP_WHITESPACE);
if (c == '\0') { /* We've arrived at the end of the file */ if (c == '\0') { /* We've arrived at the end of the file */
break; break;
...@@ -1056,11 +1152,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1056,11 +1152,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 * new section. If we actually want to replace it, the
* default case will take care of updating them. * 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); git__free(current_section);
current_section = NULL; current_section = NULL;
if (parse_section_header(cfg, &current_section) < 0) if (parse_section_header(reader, &current_section) < 0)
goto rewrite_fail; goto rewrite_fail;
/* Keep track of when it stops matching */ /* Keep track of when it stops matching */
...@@ -1069,7 +1165,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1069,7 +1165,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
} }
else if (c == ';' || c == '#') { else if (c == ';' || c == '#') {
cfg_consume_line(cfg); reader_consume_line(reader);
} }
else { else {
...@@ -1085,15 +1181,15 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1085,15 +1181,15 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
*/ */
if (!section_matches) { if (!section_matches) {
if (!last_section_matched) { if (!last_section_matched) {
cfg_consume_line(cfg); reader_consume_line(reader);
continue; continue;
} }
} else { } else {
int has_matched = 0; int has_matched = 0;
char *var_name, *var_value; char *var_name, *var_value;
pre_end = cfg->reader.read_ptr; pre_end = reader->read_ptr;
if (parse_variable(cfg, &var_name, &var_value) < 0) if (parse_variable(reader, &var_name, &var_value) < 0)
goto rewrite_fail; goto rewrite_fail;
/* First try to match the name of the variable */ /* First try to match the name of the variable */
...@@ -1112,7 +1208,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1112,7 +1208,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
if (!has_matched) if (!has_matched)
continue; continue;
post_start = cfg->reader.read_ptr; post_start = reader->read_ptr;
} }
/* We've found the variable we wanted to change, so /* We've found the variable we wanted to change, so
...@@ -1155,12 +1251,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1155,12 +1251,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
*/ */
if (write_trailer) { if (write_trailer) {
/* Write out rest of the file */ /* 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 { } else {
if (preg_replaced) { if (preg_replaced) {
git_filebuf_printf(&file, "\n%s", data_start); git_filebuf_printf(&file, "\n%s", data_start);
} else { } else {
git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
/* And now if we just need to add a variable */ /* And now if we just need to add a variable */
if (!section_matches && write_section(&file, section) < 0) if (!section_matches && write_section(&file, section) < 0)
...@@ -1176,7 +1272,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1176,7 +1272,7 @@ 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 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_write(&file, "\n", 1);
git_filebuf_printf(&file, "\t%s = %s\n", name, value); git_filebuf_printf(&file, "\t%s = %s\n", name, value);
...@@ -1187,10 +1283,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1187,10 +1283,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git__free(current_section); git__free(current_section);
/* refresh stats - if this errors, then commit will error too */ /* 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); result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
git_buf_free(&cfg->reader.buffer); git_buf_free(&reader->buffer);
return result; return result;
...@@ -1199,7 +1295,7 @@ rewrite_fail: ...@@ -1199,7 +1295,7 @@ rewrite_fail:
git__free(current_section); git__free(current_section);
git_filebuf_cleanup(&file); git_filebuf_cleanup(&file);
git_buf_free(&cfg->reader.buffer); git_buf_free(&reader->buffer);
return -1; return -1;
} }
...@@ -1291,19 +1387,19 @@ static int is_multiline_var(const char *str) ...@@ -1291,19 +1387,19 @@ static int is_multiline_var(const char *str)
return (end > str) && (count & 1); 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; char *line = NULL, *proc_line = NULL;
int quote_count; int quote_count;
/* Check that the next line exists */ /* Check that the next line exists */
line = cfg_readline(cfg, false); line = reader_readline(reader, false);
if (line == NULL) if (line == NULL)
return -1; return -1;
/* We've reached the end of the file, there is input missing */ /* We've reached the end of the file, there is input missing */
if (line[0] == '\0') { 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); git__free(line);
return -1; return -1;
} }
...@@ -1313,7 +1409,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i ...@@ -1313,7 +1409,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 it was just a comment, pretend it didn't exist */
if (line[0] == '\0') { if (line[0] == '\0') {
git__free(line); 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 */ /* TODO: unbounded recursion. This **could** be exploitable */
} }
...@@ -1338,19 +1434,19 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i ...@@ -1338,19 +1434,19 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
* keep putting stuff in the buffer * keep putting stuff in the buffer
*/ */
if (is_multiline_var(value->ptr)) if (is_multiline_var(value->ptr))
return parse_multiline_variable(cfg, value, quote_count); return parse_multiline_variable(reader, value, quote_count);
return 0; 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 *var_end = NULL;
const char *value_start = NULL; const char *value_start = NULL;
char *line; char *line;
int quote_count; int quote_count;
line = cfg_readline(cfg, true); line = reader_readline(reader, true);
if (line == NULL) if (line == NULL)
return -1; return -1;
...@@ -1385,7 +1481,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val ...@@ -1385,7 +1481,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
GITERR_CHECK_ALLOC(proc_line); GITERR_CHECK_ALLOC(proc_line);
git_buf_puts(&multi_value, proc_line); git_buf_puts(&multi_value, proc_line);
git__free(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(*var_name);
git__free(line); git__free(line);
git_buf_free(&multi_value); git_buf_free(&multi_value);
......
#include "clar_libgit2.h"
#include "buffer.h"
#include "fileops.h"
void test_config_include__relative(void)
{
git_config *cfg;
const char *str;
cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include")));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "huzzah");
git_config_free(cfg);
}
void test_config_include__absolute(void)
{
git_config *cfg;
const char *str;
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config")));
cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf));
git_buf_free(&buf);
cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute"));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "huzzah");
git_config_free(cfg);
}
void test_config_include__homedir(void)
{
git_config *cfg;
const char *str;
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config")));
cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included");
cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir"));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "huzzah");
git_config_free(cfg);
}
void test_config_include__refresh(void)
{
git_config *cfg;
const char *str;
cl_fixture_sandbox("config");
cl_git_pass(git_config_open_ondisk(&cfg, "config/config-include"));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "huzzah");
/* Change the included file and see if we refresh */
cl_git_mkfile("config/config-included", "[foo \"bar\"]\nbaz = hurrah");
cl_git_pass(git_config_refresh(cfg));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "hurrah");
git_config_free(cfg);
cl_fixture_cleanup("config");
}
/* We need to pretend that the variables were defined where the file was included */
void test_config_include__ordering(void)
{
git_config *cfg;
const char *str;
cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya");
cl_git_mkfile("including",
"[foo \"bar\"]\nfrotz = hello\n"
"[include]\npath = included\n"
"[foo \"bar\"]\nbaz = huzzah\n");
cl_git_pass(git_config_open_ondisk(&cfg, "including"));
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.frotz"));
cl_assert_equal_s(str, "hiya");
cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
cl_assert_equal_s(str, "huzzah");
git_config_free(cfg);
}
/* We need to pretend that the variables were defined where the file was included */
void test_config_include__depth(void)
{
git_config *cfg;
cl_git_mkfile("a", "[include]\npath = b");
cl_git_mkfile("b", "[include]\npath = a");
cl_git_fail(git_config_open_ondisk(&cfg, "a"));
unlink("a");
unlink("b");
}
...@@ -523,3 +523,18 @@ void test_config_read__corrupt_header(void) ...@@ -523,3 +523,18 @@ void test_config_read__corrupt_header(void)
git_config_free(cfg); git_config_free(cfg);
} }
void test_config_read__override_variable(void)
{
git_config *cfg;
const char *str;
cl_set_cleanup(&clean_test_config, NULL);
cl_git_mkfile("./testconfig", "[some] var = one\nvar = two");
cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig"));
cl_git_pass(git_config_get_string(&str, cfg, "some.var"));
cl_assert_equal_s(str, "two");
git_config_free(cfg);
}
[include]
path = config-included
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