Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/config_file.c')
-rw-r--r--src/config_file.c1110
1 files changed, 598 insertions, 512 deletions
diff --git a/src/config_file.c b/src/config_file.c
index 481c593f4..cbc48bcd9 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2011 the libgit2 contributors
+ * Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
@@ -12,14 +12,17 @@
#include "buffer.h"
#include "git2/config.h"
#include "git2/types.h"
-
+#include "strmap.h"
#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+
+GIT__USE_STRMAP;
typedef struct cvar_t {
struct cvar_t *next;
- char *section;
- char *name;
+ char *key; /* TODO: we might be able to get rid of this */
char *value;
} cvar_t;
@@ -69,10 +72,10 @@ typedef struct {
typedef struct {
git_config_file parent;
- cvar_t_list var_list;
+ git_strmap *values;
struct {
- git_fbuffer buffer;
+ git_buf buffer;
char *read_ptr;
int line_number;
int eof;
@@ -83,344 +86,379 @@ typedef struct {
static int config_parse(diskfile_backend *cfg_file);
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
-static int config_write(diskfile_backend *cfg, cvar_t *var);
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
+
+static void set_parse_error(diskfile_backend *backend, 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);
+}
static void cvar_free(cvar_t *var)
{
if (var == NULL)
return;
- git__free(var->section);
- git__free(var->name);
+ git__free(var->key);
git__free(var->value);
git__free(var);
}
-static void cvar_list_free(cvar_t_list *list)
+/* Take something the user gave us and make it nice for our hash function */
+static int normalize_name(const char *in, char **out)
{
- cvar_t *cur;
+ char *name, *fdot, *ldot;
+
+ assert(in && out);
+
+ name = git__strdup(in);
+ GITERR_CHECK_ALLOC(name);
+
+ fdot = strchr(name, '.');
+ ldot = strrchr(name, '.');
- while (!CVAR_LIST_EMPTY(list)) {
- cur = CVAR_LIST_HEAD(list);
- CVAR_LIST_REMOVE_HEAD(list);
- cvar_free(cur);
+ if (fdot == NULL || ldot == NULL) {
+ git__free(name);
+ giterr_set(GITERR_CONFIG,
+ "Invalid variable name: '%s'", in);
+ return -1;
}
+
+ /* Downcase up to the first dot and after the last one */
+ git__strntolower(name, fdot - name);
+ git__strtolower(ldot);
+
+ *out = name;
+ return 0;
}
-/*
- * Compare according to the git rules. Section contains the section as
- * it's stored internally. query is the full name as would be given to
- * 'git config'.
- */
-static int cvar_match_section(const char *section, const char *query)
+static void free_vars(git_strmap *values)
{
- const char *sdot, *qdot, *qsub;
- size_t section_len;
+ cvar_t *var = NULL;
- sdot = strchr(section, '.');
+ if (values == NULL)
+ return;
- /* If the section doesn't have any dots, it's easy */
- if (sdot == NULL)
- return !strncasecmp(section, query, strlen(section));
+ git_strmap_foreach_value(values, var,
+ while (var != NULL) {
+ cvar_t *next = CVAR_LIST_NEXT(var);
+ cvar_free(var);
+ var = next;
+ });
- /*
- * If it does have dots, compare the sections
- * case-insensitively. The comparison includes the dots.
- */
- section_len = sdot - section + 1;
- if (strncasecmp(section, query, sdot - section))
- return 0;
+ git_strmap_free(values);
+}
+
+static int config_open(git_config_file *cfg)
+{
+ int res;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+
+ b->values = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(b->values);
+
+ git_buf_init(&b->reader.buffer, 0);
+ res = git_futils_readbuffer(&b->reader.buffer, b->file_path);
- qsub = query + section_len;
- qdot = strchr(qsub, '.');
- /* Make sure the subsections are the same length */
- if (strlen(sdot + 1) != (size_t) (qdot - qsub))
+ /* It's fine if the file doesn't exist */
+ if (res == GIT_ENOTFOUND)
return 0;
- /* The subsection is case-sensitive */
- return !strncmp(sdot + 1, qsub, strlen(sdot + 1));
+ if (res < 0 || config_parse(b) < 0) {
+ free_vars(b->values);
+ b->values = NULL;
+ git_buf_free(&b->reader.buffer);
+ return -1;
+ }
+
+ git_buf_free(&b->reader.buffer);
+ return 0;
}
-static int cvar_match_name(const cvar_t *var, const char *str)
+static void backend_free(git_config_file *_backend)
{
- const char *name_start;
+ diskfile_backend *backend = (diskfile_backend *)_backend;
- if (!cvar_match_section(var->section, str)) {
- return 0;
- }
- /* Early exit if the lengths are different */
- name_start = strrchr(str, '.') + 1;
- if (strlen(var->name) != strlen(name_start))
- return 0;
+ if (backend == NULL)
+ return;
- return !strcasecmp(var->name, name_start);
+ git__free(backend->file_path);
+ free_vars(backend->values);
+ git__free(backend);
}
-static cvar_t *cvar_list_find(cvar_t_list *list, const char *name)
+static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)
{
- cvar_t *iter;
+ diskfile_backend *b = (diskfile_backend *)backend;
+ cvar_t *var;
+ const char *key;
- CVAR_LIST_FOREACH (list, iter) {
- if (cvar_match_name(iter, name))
- return iter;
- }
+ if (!b->values)
+ return 0;
+
+ git_strmap_foreach(b->values, key, var,
+ do {
+ if (fn(key, var->value, data) < 0)
+ break;
- return NULL;
+ var = CVAR_LIST_NEXT(var);
+ } while (var != NULL);
+ );
+
+ return 0;
}
-static int cvar_normalize_name(cvar_t *var, char **output)
+static int config_set(git_config_file *cfg, const char *name, const char *value)
{
- char *section_sp = strchr(var->section, ' ');
- char *quote, *name;
- size_t len;
- int ret;
+ cvar_t *var = NULL, *old_var;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ khiter_t pos;
+ int rval;
+
+ if (normalize_name(name, &key) < 0)
+ return -1;
/*
- * The final string is going to be at most one char longer than
- * the input
+ * Try to find it in the existing values and update it if it
+ * only has one value.
*/
- len = strlen(var->section) + strlen(var->name) + 1;
- name = git__malloc(len + 1);
- if (name == NULL)
- return GIT_ENOMEM;
-
- /* If there aren't any spaces in the section, it's easy */
- if (section_sp == NULL) {
- ret = p_snprintf(name, len + 1, "%s.%s", var->section, var->name);
- if (ret < 0) {
- git__free(name);
- return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno));
+ pos = git_strmap_lookup_index(b->values, key);
+ if (git_strmap_valid_index(b->values, pos)) {
+ cvar_t *existing = git_strmap_value_at(b->values, pos);
+ char *tmp = NULL;
+
+ git__free(key);
+ if (existing->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
+ return -1;
+ }
+
+ if (value) {
+ tmp = git__strdup(value);
+ GITERR_CHECK_ALLOC(tmp);
}
- *output = name;
- return GIT_SUCCESS;
+ git__free(existing->value);
+ existing->value = tmp;
+
+ return config_write(b, existing->key, NULL, value);
}
- /*
- * If there are spaces, we replace the space by a dot, move
- * section name so it overwrites the first quotation mark and
- * replace the last quotation mark by a dot. We then append the
- * variable name.
- */
- strcpy(name, var->section);
- section_sp = strchr(name, ' ');
- *section_sp = '.';
- /* Remove first quote */
- quote = strchr(name, '"');
- memmove(quote, quote+1, strlen(quote+1));
- /* Remove second quote */
- quote = strchr(name, '"');
- *quote = '.';
- strcpy(quote+1, var->name);
-
- *output = name;
- return GIT_SUCCESS;
-}
+ var = git__malloc(sizeof(cvar_t));
+ GITERR_CHECK_ALLOC(var);
-static char *interiorize_section(const char *orig)
-{
- char *dot, *last_dot, *section, *ret;
- size_t len;
+ memset(var, 0x0, sizeof(cvar_t));
- dot = strchr(orig, '.');
- last_dot = strrchr(orig, '.');
- len = last_dot - orig;
+ var->key = key;
+ var->value = NULL;
- /* No subsection, this is easy */
- if (last_dot == dot)
- return git__strndup(orig, dot - orig);
+ if (value) {
+ var->value = git__strdup(value);
+ GITERR_CHECK_ALLOC(var->value);
+ }
- section = git__strndup(orig, len);
- if (section == NULL)
- return NULL;
+ if (config_write(b, key, NULL, value) < 0) {
+ cvar_free(var);
+ return -1;
+ }
- ret = section;
- len = dot - orig;
- git__strntolower(section, len);
- return ret;
+ git_strmap_insert2(b->values, key, var, old_var, rval);
+ if (rval < 0)
+ return -1;
+ if (old_var != NULL)
+ cvar_free(old_var);
+
+ return 0;
}
-static int config_open(git_config_file *cfg)
+/*
+ * Internal function that actually gets the value in string form
+ */
+static int config_get(git_config_file *cfg, const char *name, const char **out)
{
- int error;
diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ khiter_t pos;
- error = git_futils_readbuffer(&b->reader.buffer, b->file_path);
-
- /* It's fine if the file doesn't exist */
- if (error == GIT_ENOTFOUND)
- return GIT_SUCCESS;
-
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (normalize_name(name, &key) < 0)
+ return -1;
- error = config_parse(b);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ pos = git_strmap_lookup_index(b->values, key);
+ git__free(key);
- git_futils_freebuffer(&b->reader.buffer);
+ /* no error message; the config system will write one */
+ if (!git_strmap_valid_index(b->values, pos))
+ return GIT_ENOTFOUND;
- return GIT_SUCCESS;
+ *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value;
- cleanup:
- cvar_list_free(&b->var_list);
- git_futils_freebuffer(&b->reader.buffer);
-
- return git__rethrow(error, "Failed to open config");
+ return 0;
}
-static void backend_free(git_config_file *_backend)
+static int config_get_multivar(
+ git_config_file *cfg,
+ const char *name,
+ const char *regex_str,
+ int (*fn)(const char *, void *),
+ void *data)
{
- diskfile_backend *backend = (diskfile_backend *)_backend;
+ cvar_t *var;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ khiter_t pos;
- if (backend == NULL)
- return;
+ if (normalize_name(name, &key) < 0)
+ return -1;
- git__free(backend->file_path);
- cvar_list_free(&backend->var_list);
+ pos = git_strmap_lookup_index(b->values, key);
+ git__free(key);
- git__free(backend);
-}
+ if (!git_strmap_valid_index(b->values, pos))
+ return GIT_ENOTFOUND;
-static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)
-{
- int ret = GIT_SUCCESS;
- cvar_t *var;
- diskfile_backend *b = (diskfile_backend *)backend;
+ var = git_strmap_value_at(b->values, pos);
- CVAR_LIST_FOREACH(&b->var_list, var) {
- char *normalized = NULL;
+ if (regex_str != NULL) {
+ regex_t regex;
+ int result;
- ret = cvar_normalize_name(var, &normalized);
- if (ret < GIT_SUCCESS)
- return ret;
+ /* regex matching; build the regex */
+ result = regcomp(&regex, regex_str, REG_EXTENDED);
+ if (result < 0) {
+ giterr_set_regex(&regex, result);
+ return -1;
+ }
- ret = fn(normalized, var->value, data);
- git__free(normalized);
- if (ret)
- break;
+ /* and throw the callback only on the variables that
+ * match the regex */
+ do {
+ if (regexec(&regex, var->value, 0, NULL, 0) == 0) {
+ /* early termination by the user is not an error;
+ * just break and return successfully */
+ if (fn(var->value, 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->value, data) < 0)
+ break;
+
+ var = var->next;
+ } while (var != NULL);
}
- return ret;
+ return 0;
}
-static int config_set(git_config_file *cfg, const char *name, const char *value)
+static int config_set_multivar(
+ git_config_file *cfg, const char *name, const char *regexp, const char *value)
{
- cvar_t *var = NULL;
- cvar_t *existing = NULL;
- int error = GIT_SUCCESS;
- const char *last_dot;
+ int replaced = 0;
+ cvar_t *var, *newvar;
diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ regex_t preg;
+ int result;
+ khiter_t pos;
- /*
- * If it already exists, we just need to update its value.
- */
- existing = cvar_list_find(&b->var_list, name);
- if (existing != NULL) {
- char *tmp = value ? git__strdup(value) : NULL;
- if (tmp == NULL && value != NULL)
- return GIT_ENOMEM;
+ assert(regexp);
- git__free(existing->value);
- existing->value = tmp;
+ if (normalize_name(name, &key) < 0)
+ return -1;
- return config_write(b, existing);
+ pos = git_strmap_lookup_index(b->values, key);
+ if (!git_strmap_valid_index(b->values, pos)) {
+ git__free(key);
+ return GIT_ENOTFOUND;
}
- /*
- * Otherwise, create it and stick it at the end of the queue. If
- * value is NULL, we return an error, because you can't delete a
- * variable that doesn't exist.
- */
+ var = git_strmap_value_at(b->values, pos);
- if (value == NULL)
- return git__throw(GIT_ENOTFOUND, "Can't delete non-exitent variable");
-
- last_dot = strrchr(name, '.');
- if (last_dot == NULL) {
- return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed");
+ result = regcomp(&preg, regexp, REG_EXTENDED);
+ if (result < 0) {
+ git__free(key);
+ giterr_set_regex(&preg, result);
+ return -1;
}
- var = git__malloc(sizeof(cvar_t));
- if (var == NULL)
- return GIT_ENOMEM;
+ for (;;) {
+ if (regexec(&preg, var->value, 0, NULL, 0) == 0) {
+ char *tmp = git__strdup(value);
+ GITERR_CHECK_ALLOC(tmp);
- memset(var, 0x0, sizeof(cvar_t));
+ git__free(var->value);
+ var->value = tmp;
+ replaced = 1;
+ }
- var->section = interiorize_section(name);
- if (var->section == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
+ if (var->next == NULL)
+ break;
- var->name = git__strdup(last_dot + 1);
- if (var->name == NULL) {
- error = GIT_ENOMEM;
- goto out;
+ var = var->next;
}
- var->value = value ? git__strdup(value) : NULL;
- if (var->value == NULL && value != NULL) {
- error = GIT_ENOMEM;
- goto out;
+ /* If we've reached the end of the variables and we haven't found it yet, we need to append it */
+ if (!replaced) {
+ newvar = git__malloc(sizeof(cvar_t));
+ GITERR_CHECK_ALLOC(newvar);
+
+ memset(newvar, 0x0, sizeof(cvar_t));
+
+ newvar->key = git__strdup(var->key);
+ GITERR_CHECK_ALLOC(newvar->key);
+
+ newvar->value = git__strdup(value);
+ GITERR_CHECK_ALLOC(newvar->value);
+
+ var->next = newvar;
}
- CVAR_LIST_APPEND(&b->var_list, var);
- error = config_write(b, var);
+ result = config_write(b, key, &preg, value);
- out:
- if (error < GIT_SUCCESS)
- cvar_free(var);
+ git__free(key);
+ regfree(&preg);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value");
+ return result;
}
-/*
- * Internal function that actually gets the value in string form
- */
-static int config_get(git_config_file *cfg, const char *name, const char **out)
+static int config_delete(git_config_file *cfg, const char *name)
{
cvar_t *var;
- int error = GIT_SUCCESS;
diskfile_backend *b = (diskfile_backend *)cfg;
+ char *key;
+ int result;
+ khiter_t pos;
- var = cvar_list_find(&b->var_list, name);
-
- if (var == NULL)
- return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
+ if (normalize_name(name, &key) < 0)
+ return -1;
- *out = var->value;
+ pos = git_strmap_lookup_index(b->values, key);
+ git__free(key);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name);
-}
+ if (!git_strmap_valid_index(b->values, pos))
+ return GIT_ENOTFOUND;
-static int config_delete(git_config_file *cfg, const char *name)
-{
- int error;
- cvar_t *iter, *prev = NULL;
- diskfile_backend *b = (diskfile_backend *)cfg;
+ var = git_strmap_value_at(b->values, pos);
- CVAR_LIST_FOREACH (&b->var_list, iter) {
- /* This is a bit hacky because we use a singly-linked list */
- if (cvar_match_name(iter, name)) {
- if (CVAR_LIST_HEAD(&b->var_list) == iter)
- CVAR_LIST_HEAD(&b->var_list) = CVAR_LIST_NEXT(iter);
- else
- CVAR_LIST_REMOVE_AFTER(prev);
-
- git__free(iter->value);
- iter->value = NULL;
- error = config_write(b, iter);
- cvar_free(iter);
- return error == GIT_SUCCESS ?
- GIT_SUCCESS :
- git__rethrow(error, "Failed to update config file");
- }
- /* Store it for the next round */
- prev = iter;
+ if (var->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
+ return -1;
}
- return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
+ git_strmap_delete_at(b->values, pos);
+
+ result = config_write(b, var->key, NULL, NULL);
+
+ cvar_free(var);
+ return result;
}
int git_config_file__ondisk(git_config_file **out, const char *path)
@@ -428,27 +466,25 @@ int git_config_file__ondisk(git_config_file **out, const char *path)
diskfile_backend *backend;
backend = git__malloc(sizeof(diskfile_backend));
- if (backend == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(backend);
memset(backend, 0x0, sizeof(diskfile_backend));
backend->file_path = git__strdup(path);
- if (backend->file_path == NULL) {
- git__free(backend);
- return GIT_ENOMEM;
- }
+ GITERR_CHECK_ALLOC(backend->file_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.free = backend_free;
*out = (git_config_file *)backend;
- return GIT_SUCCESS;
+ return 0;
}
static int cfg_getchar_raw(diskfile_backend *cfg)
@@ -489,7 +525,7 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags)
assert(cfg_file->reader.read_ptr);
do c = cfg_getchar_raw(cfg_file);
- while (skip_whitespace && isspace(c) &&
+ while (skip_whitespace && git__isspace(c) &&
!cfg_file->reader.eof);
if (skip_comments && (c == '#' || c == ';')) {
@@ -527,7 +563,7 @@ 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)
+static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace)
{
char *line = NULL;
char *line_src, *line_end;
@@ -535,9 +571,11 @@ static char *cfg_readline(diskfile_backend *cfg)
line_src = cfg->reader.read_ptr;
- /* Skip empty empty lines */
- while (isspace(*line_src))
- ++line_src;
+ if (skip_whitespace) {
+ /* Skip empty empty lines */
+ while (git__isspace(*line_src))
+ ++line_src;
+ }
line_end = strchr(line_src, '\n');
@@ -554,7 +592,7 @@ static char *cfg_readline(diskfile_backend *cfg)
memcpy(line, line_src, line_len);
do line[line_len] = '\0';
- while (line_len-- > 0 && isspace(line[line_len]));
+ while (line_len-- > 0 && git__isspace(line[line_len]));
if (*line_end == '\n')
line_end++;
@@ -597,12 +635,11 @@ GIT_INLINE(int) config_keychar(int c)
return isalnum(c) || c == '-';
}
-static int parse_section_header_ext(const char *line, const char *base_name, char **section_name)
+static int parse_section_header_ext(diskfile_backend *cfg, 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 error = GIT_SUCCESS;
int quote_marks;
/*
* base_name is what came before the space. We should be at the
@@ -613,8 +650,10 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha
first_quote = strchr(line, '"');
last_quote = strrchr(line, '"');
- if (last_quote - first_quote == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark");
+ if (last_quote - first_quote == 0) {
+ set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
+ return -1;
+ }
git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
git_buf_printf(&buf, "%s.", base_name);
@@ -631,26 +670,30 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha
*/
do {
if (quote_marks == 2) {
- error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote");
- goto out;
-
+ set_parse_error(cfg, rpos, "Unexpected text after closing quotes");
+ git_buf_free(&buf);
+ return -1;
}
switch (c) {
case '"':
++quote_marks;
continue;
+
case '\\':
c = line[rpos++];
+
switch (c) {
case '"':
case '\\':
break;
+
default:
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c);
- goto out;
+ set_parse_error(cfg, rpos, "Unsupported escape sequence");
+ git_buf_free(&buf);
+ return -1;
}
- break;
+
default:
break;
}
@@ -658,61 +701,53 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha
git_buf_putc(&buf, c);
} while ((c = line[rpos++]) != ']');
- *section_name = git__strdup(git_buf_cstr(&buf));
- out:
- git_buf_free(&buf);
-
- return error;
+ *section_name = git_buf_detach(&buf);
+ return 0;
}
static int parse_section_header(diskfile_backend *cfg, char **section_out)
{
char *name, *name_end;
int name_length, c, pos;
- int error = GIT_SUCCESS;
+ int result;
char *line;
- line = cfg_readline(cfg);
+ line = cfg_readline(cfg, true);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
/* find the end of the variable's name */
name_end = strchr(line, ']');
if (name_end == NULL) {
git__free(line);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end");
+ set_parse_error(cfg, 0, "Missing ']' in section header");
+ return -1;
}
name = (char *)git__malloc((size_t)(name_end - line) + 1);
- if (name == NULL) {
- git__free(line);
- return GIT_ENOMEM;
- }
+ GITERR_CHECK_ALLOC(name);
name_length = 0;
pos = 0;
/* Make sure we were given a section header */
c = line[pos++];
- if (c != '[') {
- error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug");
- goto error;
- }
+ assert(c == '[');
c = line[pos++];
do {
- if (isspace(c)){
+ if (git__isspace(c)){
name[name_length] = '\0';
- error = parse_section_header_ext(line, name, section_out);
+ result = parse_section_header_ext(cfg, line, name, section_out);
git__free(line);
git__free(name);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header");
+ return result;
}
if (!config_keychar(c) && c != '.') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header");
- goto error;
+ set_parse_error(cfg, pos, "Unexpected character in header");
+ goto fail_parse;
}
name[name_length++] = (char) tolower(c);
@@ -720,28 +755,29 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out)
} while ((c = line[pos++]) != ']');
if (line[pos - 1] != ']') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly");
- goto error;
+ set_parse_error(cfg, pos, "Unexpected end of file");
+ goto fail_parse;
}
- name[name_length] = 0;
git__free(line);
- git__strtolower(name);
+
+ name[name_length] = 0;
*section_out = name;
- return GIT_SUCCESS;
-error:
+ return 0;
+
+fail_parse:
git__free(line);
git__free(name);
- return error;
+ return -1;
}
static int skip_bom(diskfile_backend *cfg)
{
- static const char *utf8_bom = "\xef\xbb\xbf";
+ static const char utf8_bom[] = "\xef\xbb\xbf";
- if (cfg->reader.buffer.len < sizeof(utf8_bom))
- return GIT_SUCCESS;
+ if (cfg->reader.buffer.size < sizeof(utf8_bom))
+ return 0;
if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
cfg->reader.read_ptr += sizeof(utf8_bom);
@@ -750,7 +786,7 @@ static int skip_bom(diskfile_backend *cfg)
shit with the BoM
*/
- return GIT_SUCCESS;
+ return 0;
}
/*
@@ -792,9 +828,9 @@ static int skip_bom(diskfile_backend *cfg)
boolean_false = "no" | "0" | "false" | "off"
*/
-static void strip_comments(char *line)
+static int strip_comments(char *line, int in_quotes)
{
- int quote_count = 0;
+ int quote_count = in_quotes;
char *ptr;
for (ptr = line; *ptr; ++ptr) {
@@ -807,30 +843,37 @@ static void strip_comments(char *line)
}
}
- if (isspace(ptr[-1])) {
- /* TODO skip whitespace */
+ /* skip any space at the end */
+ if (git__isspace(ptr[-1])) {
+ ptr--;
}
+ ptr[0] = '\0';
+
+ return quote_count;
}
static int config_parse(diskfile_backend *cfg_file)
{
- int error = GIT_SUCCESS, c;
+ int c;
char *current_section = NULL;
char *var_name;
char *var_value;
- cvar_t *var;
+ cvar_t *var, *existing;
+ git_buf buf = GIT_BUF_INIT;
+ int result = 0;
+ khiter_t pos;
/* Initialize the reading position */
- cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
+ cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr;
cfg_file->reader.eof = 0;
/* If the file is empty, there's nothing for us to do */
if (*cfg_file->reader.read_ptr == '\0')
- return GIT_SUCCESS;
+ return 0;
skip_bom(cfg_file);
- while (error == GIT_SUCCESS && !cfg_file->reader.eof) {
+ while (result == 0 && !cfg_file->reader.eof) {
c = cfg_peek(cfg_file, SKIP_WHITESPACE);
@@ -842,7 +885,7 @@ static int config_parse(diskfile_backend *cfg_file)
case '[': /* section header, new section begins */
git__free(current_section);
current_section = NULL;
- error = parse_section_header(cfg_file, &current_section);
+ result = parse_section_header(cfg_file, &current_section);
break;
case ';':
@@ -851,100 +894,119 @@ static int config_parse(diskfile_backend *cfg_file)
break;
default: /* assume variable declaration */
- error = parse_variable(cfg_file, &var_name, &var_value);
-
- if (error < GIT_SUCCESS)
+ result = parse_variable(cfg_file, &var_name, &var_value);
+ if (result < 0)
break;
var = git__malloc(sizeof(cvar_t));
- if (var == NULL) {
- error = GIT_ENOMEM;
- break;
- }
+ GITERR_CHECK_ALLOC(var);
memset(var, 0x0, sizeof(cvar_t));
- var->section = git__strdup(current_section);
- if (var->section == NULL) {
- error = GIT_ENOMEM;
- git__free(var);
- break;
- }
+ git__strtolower(var_name);
+ git_buf_printf(&buf, "%s.%s", current_section, var_name);
+ git__free(var_name);
- var->name = var_name;
+ if (git_buf_oom(&buf))
+ return -1;
+
+ var->key = git_buf_detach(&buf);
var->value = var_value;
- git__strtolower(var->name);
- CVAR_LIST_APPEND(&cfg_file->var_list, var);
+ /* Add or append the new config option */
+ pos = git_strmap_lookup_index(cfg_file->values, var->key);
+ if (!git_strmap_valid_index(cfg_file->values, pos)) {
+ git_strmap_insert(cfg_file->values, var->key, var, result);
+ if (result < 0)
+ break;
+ result = 0;
+ } else {
+ existing = git_strmap_value_at(cfg_file->values, pos);
+ while (existing->next != NULL) {
+ existing = existing->next;
+ }
+ existing->next = var;
+ }
break;
}
}
git__free(current_section);
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config");
+ return result;
}
-static int write_section(git_filebuf *file, cvar_t *var)
+static int write_section(git_filebuf *file, const char *key)
{
- int error;
+ int result;
+ const char *dot;
+ git_buf buf = GIT_BUF_INIT;
- error = git_filebuf_printf(file, "[%s]\n", var->section);
- if (error < GIT_SUCCESS)
- return error;
+ /* All of this just for [section "subsection"] */
+ dot = strchr(key, '.');
+ git_buf_putc(&buf, '[');
+ if (dot == NULL) {
+ git_buf_puts(&buf, key);
+ } else {
+ git_buf_put(&buf, key, dot - key);
+ /* TODO: escape */
+ git_buf_printf(&buf, " \"%s\"", dot + 1);
+ }
+ git_buf_puts(&buf, "]\n");
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
+ git_buf_free(&buf);
- error = git_filebuf_printf(file, " %s = %s\n", var->name, var->value);
- return error;
+ return result;
}
/*
* This is pretty much the parsing, except we write out anything we don't have
*/
-static int config_write(diskfile_backend *cfg, cvar_t *var)
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
{
- int error = GIT_SUCCESS, c;
- int section_matches = 0, last_section_matched = 0;
- char *current_section = NULL;
- char *var_name, *var_value, *data_start;
+ 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;
+ char *current_section = NULL, *section, *name, *ldot;
git_filebuf file = GIT_FILEBUF_INIT;
- const char *pre_end = NULL, *post_start = NULL;
/* We need to read in our own config file */
- error = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
- if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) {
- return git__rethrow(error, "Failed to read existing config file %s", cfg->file_path);
- }
+ result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
/* Initialise the reading position */
- if (error == GIT_ENOTFOUND) {
- error = GIT_SUCCESS;
+ if (result == GIT_ENOTFOUND) {
cfg->reader.read_ptr = NULL;
cfg->reader.eof = 1;
data_start = NULL;
- cfg->reader.buffer.len = 0;
- cfg->reader.buffer.data = NULL;
- } else {
- cfg->reader.read_ptr = cfg->reader.buffer.data;
+ git_buf_clear(&cfg->reader.buffer);
+ } else if (result == 0) {
+ cfg->reader.read_ptr = cfg->reader.buffer.ptr;
cfg->reader.eof = 0;
data_start = cfg->reader.read_ptr;
+ } else {
+ return -1; /* OS error when reading the file */
}
/* Lock the file */
- error = git_filebuf_open(&file, cfg->file_path, 0);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lock config file");
+ if (git_filebuf_open(&file, cfg->file_path, 0) < 0)
+ return -1;
skip_bom(cfg);
+ ldot = strrchr(key, '.');
+ name = ldot + 1;
+ section = git__strndup(key, ldot - key);
- while (error == GIT_SUCCESS && !cfg->reader.eof) {
+ while (!cfg->reader.eof) {
c = cfg_peek(cfg, SKIP_WHITESPACE);
- switch (c) {
- case '\0': /* We've arrived at the end of the file */
+ if (c == '\0') { /* We've arrived at the end of the file */
break;
- case '[': /* section header, new section begins */
+ } else if (c == '[') { /* section header, new section begins */
/*
* We set both positions to the current one in case we
* need to add a variable to the end of a section. In that
@@ -953,23 +1015,22 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
* default case will take care of updating them.
*/
pre_end = post_start = cfg->reader.read_ptr;
- if (current_section)
- git__free(current_section);
- error = parse_section_header(cfg, &current_section);
- if (error < GIT_SUCCESS)
- break;
+
+ git__free(current_section);
+ current_section = NULL;
+ if (parse_section_header(cfg, &current_section) < 0)
+ goto rewrite_fail;
/* Keep track of when it stops matching */
last_section_matched = section_matches;
- section_matches = !strcmp(current_section, var->section);
- break;
+ section_matches = !strcmp(current_section, section);
+ }
- case ';':
- case '#':
+ else if (c == ';' || c == '#') {
cfg_consume_line(cfg);
- break;
+ }
- default:
+ else {
/*
* If the section doesn't match, but the last section did,
* it means we need to add a variable (so skip the line
@@ -983,58 +1044,54 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
if (!section_matches) {
if (!last_section_matched) {
cfg_consume_line(cfg);
- break;
+ continue;
}
} else {
- int cmp = -1;
+ int has_matched = 0;
+ char *var_name, *var_value;
pre_end = cfg->reader.read_ptr;
- if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS)
- cmp = strcasecmp(var->name, var_name);
+ if (parse_variable(cfg, &var_name, &var_value) < 0)
+ goto rewrite_fail;
+
+ /* First try to match the name of the variable */
+ if (strcasecmp(name, var_name) == 0)
+ has_matched = 1;
+
+ /* If the name matches, and we have a regex to match the
+ * value, try to match it */
+ if (has_matched && preg != NULL)
+ has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0);
git__free(var_name);
git__free(var_value);
- if (cmp != 0)
- break;
+ /* if there is no match, keep going */
+ if (!has_matched)
+ continue;
post_start = cfg->reader.read_ptr;
}
- /*
- * We've found the variable we wanted to change, so
- * write anything up to it
- */
- error = git_filebuf_write(&file, data_start, pre_end - data_start);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write the first part of the file");
- break;
- }
+ /* 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);
+ preg_replaced = 1;
- /*
- * Then replace the variable. If the value is NULL, it
- * means we want to delete it, so pretend everything went
- * fine
- */
- if (var->value == NULL)
- error = GIT_SUCCESS;
- else
- error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to overwrite the variable");
- break;
+ /* 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);
}
- /* And then the write out rest of the file */
- error = git_filebuf_write(&file, post_start,
- cfg->reader.buffer.len - (post_start - data_start));
-
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write the rest of the file");
- break;
+ /* multiline variable? we need to keep reading lines to match */
+ if (preg != NULL) {
+ data_start = post_start;
+ continue;
}
- goto cleanup;
+ write_trailer = 1;
+ break; /* break from the loop */
}
}
@@ -1047,127 +1104,166 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
* 2) we didn't find a section for us so we need to create it
* ourselves.
*
- * Either way we need to write out the whole file.
+ * 3) we're setting a multivar with a regex, which means we
+ * continue to search for matching values
+ *
+ * In the last case, if we've already replaced a value, we
+ * want to write the rest of the file. Otherwise we need to write
+ * out the whole file and then the new variable.
*/
+ if (write_trailer) {
+ /* Write out rest of the file */
+ git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start));
+ } else {
+ if (preg_replaced) {
+ git_filebuf_printf(&file, "\n%s", data_start);
+ } else {
+ git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size);
+
+ /* And now if we just need to add a variable */
+ if (!section_matches && write_section(&file, section) < 0)
+ goto rewrite_fail;
+
+ /* Sanity check: if we are here, and value is NULL, that means that somebody
+ * touched the config file after our intial read. We should probably assert()
+ * this, but instead we'll handle it gracefully with an error. */
+ if (value == NULL) {
+ giterr_set(GITERR_CONFIG,
+ "Race condition when writing a config file (a cvar has been removed)");
+ goto rewrite_fail;
+ }
- error = git_filebuf_write(&file, cfg->reader.buffer.data, cfg->reader.buffer.len);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write original config content");
- goto cleanup;
+ git_filebuf_printf(&file, "\t%s = %s\n", name, value);
+ }
}
- /* And now if we just need to add a variable */
- if (section_matches) {
- error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
- goto cleanup;
- }
+ git__free(section);
+ git__free(current_section);
- /* Or maybe we need to write out a whole section */
- error = write_section(&file, var);
- if (error < GIT_SUCCESS)
- git__rethrow(error, "Failed to write new section");
+ result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
+ git_buf_free(&cfg->reader.buffer);
+ return result;
- cleanup:
+rewrite_fail:
+ git__free(section);
git__free(current_section);
- if (error < GIT_SUCCESS)
- git_filebuf_cleanup(&file);
- else
- error = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
-
- git_futils_freebuffer(&cfg->reader.buffer);
- return error;
+ git_filebuf_cleanup(&file);
+ git_buf_free(&cfg->reader.buffer);
+ return -1;
}
-static int is_multiline_var(const char *str)
+/* '\"' -> '"' etc */
+static char *fixup_line(const char *ptr, int quote_count)
{
- char *end = strrchr(str, '\0') - 1;
+ char *str = git__malloc(strlen(ptr) + 1);
+ char *out = str, *esc;
+ const char *escapes = "ntb\"\\";
+ const char *escaped = "\n\t\b\"\\";
- while (isspace(*end))
- --end;
+ if (str == NULL)
+ return NULL;
- return *end == '\\';
+ while (*ptr != '\0') {
+ if (*ptr == '"') {
+ quote_count++;
+ } else if (*ptr != '\\') {
+ *out++ = *ptr;
+ } else {
+ /* backslash, check the next char */
+ ptr++;
+ /* if we're at the end, it's a multiline, so keep the backslash */
+ if (*ptr == '\0') {
+ *out++ = '\\';
+ goto out;
+ }
+ if ((esc = strchr(escapes, *ptr)) != NULL) {
+ *out++ = escaped[esc - escapes];
+ } else {
+ git__free(str);
+ giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
+ return NULL;
+ }
+ }
+ ptr++;
+ }
+
+out:
+ *out = '\0';
+
+ return str;
}
-static int parse_multiline_variable(diskfile_backend *cfg, const char *first, char **out)
+static int is_multiline_var(const char *str)
{
- char *line = NULL, *end;
- int error = GIT_SUCCESS, ret;
- size_t len;
- char *buf;
+ const char *end = str + strlen(str);
+ return (end > str) && (end[-1] == '\\');
+}
+
+static int parse_multiline_variable(diskfile_backend *cfg, 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);
+ line = cfg_readline(cfg, false);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
/* We've reached the end of the file, there is input missing */
if (line[0] == '\0') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly");
- goto out;
+ set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var");
+ git__free(line);
+ return -1;
}
- strip_comments(line);
+ quote_count = strip_comments(line, !!in_quotes);
/* If it was just a comment, pretend it didn't exist */
if (line[0] == '\0') {
- error = parse_multiline_variable(cfg, first, out);
- goto out;
+ git__free(line);
+ return parse_multiline_variable(cfg, value, quote_count);
+ /* TODO: unbounded recursion. This **could** be exploitable */
}
- /* Find the continuation character '\' and strip the whitespace */
- end = strrchr(first, '\\');
- while (isspace(end[-1]))
- --end;
-
- *end = '\0'; /* Terminate the string here */
-
- len = strlen(first) + strlen(line) + 2;
- buf = git__malloc(len);
- if (buf == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
+ /* Drop the continuation character '\': to closely follow the UNIX
+ * 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);
- ret = p_snprintf(buf, len, "%s %s", first, line);
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno));
- git__free(buf);
- goto out;
+ proc_line = fixup_line(line, in_quotes);
+ if (proc_line == NULL) {
+ git__free(line);
+ return -1;
}
+ /* add this line to the multiline var */
+ git_buf_puts(value, proc_line);
+ git__free(line);
+ git__free(proc_line);
/*
- * If we need to continue reading the next line, pretend
- * everything we've read up to now was in one line and call
- * ourselves.
+ * If we need to continue reading the next line, let's just
+ * keep putting stuff in the buffer
*/
- if (is_multiline_var(buf)) {
- char *final_val;
- error = parse_multiline_variable(cfg, buf, &final_val);
- git__free(buf);
- buf = final_val;
- }
-
- *out = buf;
+ if (is_multiline_var(value->ptr))
+ return parse_multiline_variable(cfg, value, quote_count);
- out:
- git__free(line);
- return error;
+ return 0;
}
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
{
- char *tmp;
- int error = GIT_SUCCESS;
const char *var_end = NULL;
const char *value_start = NULL;
char *line;
+ int quote_count;
- line = cfg_readline(cfg);
+ line = cfg_readline(cfg, true);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
- strip_comments(line);
+ quote_count = strip_comments(line, 0);
var_end = strchr(line, '=');
@@ -1176,57 +1272,47 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
else
value_start = var_end + 1;
- if (isspace(var_end[-1])) {
+ if (git__isspace(var_end[-1])) {
do var_end--;
- while (isspace(var_end[0]));
+ while (git__isspace(var_end[0]));
}
- tmp = git__strndup(line, var_end - line + 1);
- if (tmp == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
+ *var_name = git__strndup(line, var_end - line + 1);
+ GITERR_CHECK_ALLOC(*var_name);
- *var_name = tmp;
+ /* If there is no value, boolean true is assumed */
+ *var_value = NULL;
/*
* Now, let's try to parse the value
*/
if (value_start != NULL) {
-
- while (isspace(value_start[0]))
+ while (git__isspace(value_start[0]))
value_start++;
- if (value_start[0] == '\0') {
- *var_value = NULL;
- goto out;
- }
-
if (is_multiline_var(value_start)) {
- error = parse_multiline_variable(cfg, value_start, var_value);
- if (error != GIT_SUCCESS)
- {
- *var_value = NULL;
+ git_buf multi_value = GIT_BUF_INIT;
+ char *proc_line = fixup_line(value_start, 0);
+ 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)) {
git__free(*var_name);
+ git__free(line);
+ git_buf_free(&multi_value);
+ return -1;
}
- goto out;
- }
- tmp = git__strdup(value_start);
- if (tmp == NULL) {
- git__free(*var_name);
- *var_value = NULL;
- error = GIT_ENOMEM;
- goto out;
+ *var_value = git_buf_detach(&multi_value);
+
+ }
+ else if (value_start[0] != '\0') {
+ *var_value = fixup_line(value_start, 0);
+ GITERR_CHECK_ALLOC(*var_value);
}
- *var_value = tmp;
- } else {
- /* If there is no value, boolean true is assumed */
- *var_value = NULL;
}
- out:
git__free(line);
- return error;
+ return 0;
}