]> git.proxmox.com Git - libgit2.git/blobdiff - src/config.c
error-handling: On-disk config file backend
[libgit2.git] / src / config.c
index f53afa1452dda78fb02e0eaecbd01441bf1b9377..77598d6a62502ca2b0d052fd1329ecd89b121e70 100644 (file)
@@ -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.
@@ -22,24 +22,29 @@ typedef struct {
        int priority;
 } file_internal;
 
-void git_config_free(git_config *cfg)
+static void config_free(git_config *cfg)
 {
        unsigned int i;
        git_config_file *file;
        file_internal *internal;
 
-       if (cfg == NULL)
-               return;
-
        for(i = 0; i < cfg->files.length; ++i){
                internal = git_vector_get(&cfg->files, i);
                file = internal->file;
                file->free(file);
-               free(internal);
+               git__free(internal);
        }
 
        git_vector_free(&cfg->files);
-       free(cfg);
+       git__free(cfg);
+}
+
+void git_config_free(git_config *cfg)
+{
+       if (cfg == NULL)
+               return;
+
+       GIT_REFCOUNT_DEC(cfg, config_free);
 }
 
 static int config_backend_cmp(const void *a, const void *b)
@@ -61,78 +66,72 @@ int git_config_new(git_config **out)
        memset(cfg, 0x0, sizeof(git_config));
 
        if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) {
-               free(cfg);
-               return GIT_ENOMEM;
+               git__free(cfg);
+               return -1;
        }
 
        *out = cfg;
-
-       return GIT_SUCCESS;
+       GIT_REFCOUNT_INC(cfg);
+       return 0;
 }
 
 int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority)
 {
        git_config_file *file = NULL;
-       int error;
 
-       error = git_config_file__ondisk(&file, path);
-       if (error < GIT_SUCCESS)
-               return error;
+       if (git_config_file__ondisk(&file, path) < 0)
+               return -1;
 
-       error = git_config_add_file(cfg, file, priority);
-       if (error < GIT_SUCCESS) {
+       if (git_config_add_file(cfg, file, priority) < 0) {
                /*
                 * free manually; the file is not owned by the config
                 * instance yet and will not be freed on cleanup
                 */
                file->free(file);
-               return error;
+               return -1;
        }
 
-       return GIT_SUCCESS;
+       return 0;
 }
 
 int git_config_open_ondisk(git_config **cfg, const char *path)
 {
-       int error;
-
-       error = git_config_new(cfg);
-       if (error < GIT_SUCCESS)
-               return error;
+       if (git_config_new(cfg) < 0)
+               return -1;
 
-       error = git_config_add_file_ondisk(*cfg, path, 1);
-       if (error < GIT_SUCCESS)
+       if (git_config_add_file_ondisk(*cfg, path, 1) < 0) {
                git_config_free(*cfg);
+               return -1;
+       }
 
-       return error;
+       return 0;
 }
 
 int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
 {
        file_internal *internal;
-       int error;
+       int result;
 
        assert(cfg && file);
 
-       if ((error = file->open(file)) < GIT_SUCCESS)
-               return git__rethrow(error, "Failed to open config file");
+       if ((result = file->open(file)) < 0)
+               return result;
 
        internal = git__malloc(sizeof(file_internal));
-       if (internal == NULL)
-               return GIT_ENOMEM;
+       GITERR_CHECK_ALLOC(internal);
 
        internal->file = file;
        internal->priority = priority;
 
        if (git_vector_insert(&cfg->files, internal) < 0) {
-               free(internal);
-               return GIT_ENOMEM;
+               git__free(internal);
+               return -1;
        }
 
        git_vector_sort(&cfg->files);
        internal->file->cfg = cfg;
 
-       return GIT_SUCCESS;
+       return 0;
 }
 
 /*
@@ -157,7 +156,15 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, vo
 
 int git_config_delete(git_config *cfg, const char *name)
 {
-       return git_config_set_string(cfg, name, NULL);
+       file_internal *internal;
+       git_config_file *file;
+
+       assert(cfg->files.length);
+
+       internal = git_vector_get(&cfg->files, 0);
+       file = internal->file;
+
+       return file->del(file, name);
 }
 
 /**************
@@ -186,8 +193,7 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
        file_internal *internal;
        git_config_file *file;
 
-       if (cfg->files.length == 0)
-               return git__throw(GIT_EINVALIDARGS, "Cannot set variable value; no files open in the `git_config` instance");
+       assert(cfg->files.length);
 
        internal = git_vector_get(&cfg->files, 0);
        file = internal->file;
@@ -195,23 +201,37 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
        return file->set(file, name, value);
 }
 
-/***********
- * Getters
- ***********/
+static int parse_bool(int *out, const char *value)
+{
+       /* A missing value means true */
+       if (value == NULL) {
+               *out = 1;
+               return GIT_SUCCESS;
+       }
 
-int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
+       if (!strcasecmp(value, "true") ||
+               !strcasecmp(value, "yes") ||
+               !strcasecmp(value, "on")) {
+               *out = 1;
+               return GIT_SUCCESS;
+       }
+       if (!strcasecmp(value, "false") ||
+               !strcasecmp(value, "no") ||
+               !strcasecmp(value, "off")) {
+               *out = 0;
+               return GIT_SUCCESS;
+       }
+
+       return GIT_EINVALIDTYPE;
+}
+
+static int parse_int64(int64_t *out, const char *value)
 {
-       const char *value, *num_end;
-       int ret;
+       const char *num_end;
        int64_t num;
 
-       ret = git_config_get_string(cfg, name, &value);
-       if (ret < GIT_SUCCESS)
-               return git__rethrow(ret, "Failed to retrieve value for '%s'", name);
-
-       ret = git__strtol64(&num, value, &num_end, 0);
-       if (ret < GIT_SUCCESS)
-               return git__rethrow(ret, "Failed to convert value for '%s'", name);
+       if (git__strtol64(&num, value, &num_end, 0) < 0)
+               return -1;
 
        switch (*num_end) {
        case 'g':
@@ -231,173 +251,263 @@ int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
                /* check that that there are no more characters after the
                 * given modifier suffix */
                if (num_end[1] != '\0')
-                       return git__throw(GIT_EINVALIDTYPE,
-                               "Failed to get value for '%s'. Invalid type suffix", name);
+                       return -1;
 
                /* fallthrough */
 
        case '\0':
                *out = num;
-               return GIT_SUCCESS;
+               return 0;
 
        default:
-               return git__throw(GIT_EINVALIDTYPE,
-                       "Failed to get value for '%s'. Value is of invalid type", name);
+               return -1;
        }
 }
 
-int git_config_get_int32(git_config *cfg, const char *name, int32_t *out)
+static int parse_int32(int32_t *out, const char *value)
 {
-       int64_t tmp_long;
-       int32_t tmp_int;
-       int ret;
+       int64_t tmp;
+       int32_t truncate;
 
-       ret = git_config_get_int64(cfg, name, &tmp_long);
-       if (ret < GIT_SUCCESS)
-               return git__rethrow(ret, "Failed to convert value for '%s'", name);
-       
-       tmp_int = tmp_long & 0xFFFFFFFF;
-       if (tmp_int != tmp_long)
-               return git__throw(GIT_EOVERFLOW, "Value for '%s' is too large", name);
+       if (parse_int64(&tmp, value) < 0)
+               return -1;
 
-       *out = tmp_int;
+       truncate = tmp & 0xFFFFFFFF;
+       if (truncate != tmp)
+               return -1;
 
-       return ret;
+       *out = truncate;
+       return 0;
 }
 
-int git_config_get_bool(git_config *cfg, const char *name, int *out)
+/***********
+ * Getters
+ ***********/
+int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out)
 {
+       size_t i;
        const char *value;
-       int error = GIT_SUCCESS;
+       int error;
 
        error = git_config_get_string(cfg, name, &value);
        if (error < GIT_SUCCESS)
-               return git__rethrow(error, "Failed to get value for %s", name);
+               return error;
 
-       /* A missing value means true */
-       if (value == NULL) {
-               *out = 1;
-               return GIT_SUCCESS;
+       for (i = 0; i < map_n; ++i) {
+               git_cvar_map *m = maps + i;
+
+               switch (m->cvar_type) {
+                       case GIT_CVAR_FALSE:
+                       case GIT_CVAR_TRUE: {
+                               int bool_val;
+
+                               if (parse_bool(&bool_val, value) == 0 && 
+                                       bool_val == (int)m->cvar_type) {
+                                       *out = m->map_value;
+                                       return 0;
+                               }
+
+                               break;
+                       }
+
+                       case GIT_CVAR_INT32:
+                               if (parse_int32(out, value) == 0)
+                                       return 0;
+
+                               break;
+
+                       case GIT_CVAR_STRING:
+                               if (strcasecmp(value, m->str_match) == 0) {
+                                       *out = m->map_value;
+                                       return 0;
+                               }
+               }
        }
 
-       if (!strcasecmp(value, "true") ||
-               !strcasecmp(value, "yes") ||
-               !strcasecmp(value, "on")) {
-               *out = 1;
-               return GIT_SUCCESS;
+       giterr_set(GITERR_CONFIG,
+               "Failed to map the '%s' config variable with a valid value", name);
+       return -1;
+}
+
+int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
+{
+       const char *value;
+       int ret;
+
+       ret = git_config_get_string(cfg, name, &value);
+       if (ret < 0)
+               return ret;
+
+       if (parse_int64(out, value) < 0) {
+               giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
+               return -1;
        }
-       if (!strcasecmp(value, "false") ||
-               !strcasecmp(value, "no") ||
-               !strcasecmp(value, "off")) {
-               *out = 0;
-               return GIT_SUCCESS;
+
+       return 0;
+}
+
+int git_config_get_int32(git_config *cfg, const char *name, int32_t *out)
+{
+       const char *value;
+       int ret;
+
+       ret = git_config_get_string(cfg, name, &value);
+       if (ret < 0)
+               return ret;
+
+       if (parse_int32(out, value) < 0) {
+               giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
+               return -1;
        }
+       
+       return 0;
+}
+
+int git_config_get_bool(git_config *cfg, const char *name, int *out)
+{
+       const char *value;
+       int ret;
+
+       ret = git_config_get_string(cfg, name, &value);
+       if (ret < 0)
+               return ret;
 
-       /* Try to parse it as an integer */
-       error = git_config_get_int32(cfg, name, out);
-       if (error == GIT_SUCCESS)
+       if (parse_bool(out, value) == 0)
+               return 0;
+
+       if (parse_int32(out, value) == 0) {
                *out = !!(*out);
+               return 0;
+       }
 
-       if (error < GIT_SUCCESS)
-               return git__rethrow(error, "Failed to get value for %s", name);
-       return error;
+       giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
+       return -1;
 }
 
 int git_config_get_string(git_config *cfg, const char *name, const char **out)
 {
        file_internal *internal;
        git_config_file *file;
-       int error = GIT_ENOTFOUND;
+       int ret = GIT_ENOTFOUND;
        unsigned int i;
 
-       if (cfg->files.length == 0)
-               return git__throw(GIT_EINVALIDARGS, "Cannot get variable value; no files open in the `git_config` instance");
+       assert(cfg->files.length);
 
        for (i = 0; i < cfg->files.length; ++i) {
                internal = git_vector_get(&cfg->files, i);
                file = internal->file;
-               if ((error = file->get(file, name, out)) == GIT_SUCCESS)
-                       return GIT_SUCCESS;
+
+               ret = file->get(file, name, out);
+               if (ret == 0)
+                       return 0;
+
+               /* File backend doesn't set error message on variable
+                * not found */
+               if (ret == GIT_ENOTFOUND)
+                       continue;
+
+               return ret;
        }
 
-       return git__throw(error, "Config value '%s' not found", name);
+       giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name);
+       return GIT_ENOTFOUND;
 }
 
-int git_config_find_global(char *global_config_path)
+int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp,
+                           int (*fn)(const char *value, void *data), void *data)
 {
-       const char *home;
+       file_internal *internal;
+       git_config_file *file;
+       int ret = GIT_ENOTFOUND;
+       unsigned int i;
 
-       home = getenv("HOME");
+       assert(cfg->files.length);
 
-#ifdef GIT_WIN32
-       if (home == NULL)
-               home = getenv("USERPROFILE");
-#endif
+       /*
+        * This loop runs the "wrong" way 'round because we need to
+        * look at every value from the most general to most specific
+        */
+       for (i = cfg->files.length; i > 0; --i) {
+               internal = git_vector_get(&cfg->files, i - 1);
+               file = internal->file;
+               ret = file->get_multivar(file, name, regexp, fn, data);
+               if (ret < 0 && ret != GIT_ENOTFOUND)
+                       return ret;
+       }
 
-       if (home == NULL)
-               return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot locate the user's home directory");
+       return 0;
+}
 
-       git_path_join(global_config_path, home, GIT_CONFIG_FILENAME);
+int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
+{
+       file_internal *internal;
+       git_config_file *file;
+       int ret = GIT_ENOTFOUND;
+       unsigned int i;
 
-       if (git_futils_exists(global_config_path) < GIT_SUCCESS)
-               return git__throw(GIT_EOSERR, "Failed to open global config file. The file does not exist");
+       for (i = cfg->files.length; i > 0; --i) {
+               internal = git_vector_get(&cfg->files, i - 1);
+               file = internal->file;
+               ret = file->set_multivar(file, name, regexp, value);
+               if (ret < GIT_SUCCESS && ret != GIT_ENOTFOUND)
+                       return ret;
+       }
 
-       return GIT_SUCCESS;
+       return 0;
 }
 
-
-
-#if GIT_WIN32
-static int win32_find_system(char *system_config_path)
+int git_config_find_global_r(git_buf *path)
 {
-       const wchar_t *query = L"%PROGRAMFILES%\\Git\\etc\\gitconfig";
-       wchar_t *apphome_utf16;
-       char *apphome_utf8;
-       DWORD size, ret;
-
-       size = ExpandEnvironmentStringsW(query, NULL, 0);
-       /* The function gave us the full size of the buffer in chars, including NUL */
-       apphome_utf16 = git__malloc(size * sizeof(wchar_t));
-       if (apphome_utf16 == NULL)
-               return GIT_ENOMEM;
+       return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
+}
 
-       ret = ExpandEnvironmentStringsW(query, apphome_utf16, size);
-       if (ret != size)
-               return git__throw(GIT_ERROR, "Failed to expand environment strings");
+int git_config_find_global(char *global_config_path)
+{
+       git_buf path  = GIT_BUF_INIT;
+       int     ret = git_config_find_global_r(&path);
 
-       if (_waccess(apphome_utf16, F_OK) < 0) {
-               free(apphome_utf16);
-               return GIT_ENOTFOUND;
+       if (ret < 0) {
+               git_buf_free(&path);
+               return ret;
        }
 
-       apphome_utf8 = conv_utf16_to_utf8(apphome_utf16);
-       free(apphome_utf16);
-
-       if (strlen(apphome_utf8) >= GIT_PATH_MAX) {
-               free(apphome_utf8);
-               return git__throw(GIT_ESHORTBUFFER, "Path is too long");
+       if (path.size > GIT_PATH_MAX) {
+               git_buf_free(&path);
+               giterr_set(GITERR_NOMEMORY,
+                       "Path is to long to fit on the given buffer");
+               return -1;
        }
 
-       strcpy(system_config_path, apphome_utf8);
-       free(apphome_utf8);
-       return GIT_SUCCESS;
+       git_buf_copy_cstr(global_config_path, GIT_PATH_MAX, &path);
+       git_buf_free(&path);
+       return 0;
+}
+
+int git_config_find_system_r(git_buf *path)
+{
+       return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
 }
-#endif
 
 int git_config_find_system(char *system_config_path)
 {
-       const char *etc = "/etc/gitconfig";
+       git_buf path  = GIT_BUF_INIT;
+       int     ret = git_config_find_system_r(&path);
 
-       if (git_futils_exists(etc) == GIT_SUCCESS) {
-               memcpy(system_config_path, etc, strlen(etc) + 1);
-               return GIT_SUCCESS;
+       if (ret < 0) {
+               git_buf_free(&path);
+               return ret;
        }
 
-#if GIT_WIN32
-       return win32_find_system(system_config_path);
-#else
-       return GIT_ENOTFOUND;
-#endif
+       if (path.size > GIT_PATH_MAX) {
+               git_buf_free(&path);
+               giterr_set(GITERR_NOMEMORY,
+                       "Path is to long to fit on the given buffer");
+               return -1;
+       }
+
+       git_buf_copy_cstr(system_config_path, GIT_PATH_MAX, &path);
+       git_buf_free(&path);
+       return 0;
 }
 
 int git_config_open_global(git_config **out)
@@ -405,7 +515,7 @@ int git_config_open_global(git_config **out)
        int error;
        char global_path[GIT_PATH_MAX];
 
-       if ((error = git_config_find_global(global_path)) < GIT_SUCCESS)
+       if ((error = git_config_find_global(global_path)) < 0)
                return error;
 
        return git_config_open_ondisk(out, global_path);